From c1faa421ea2219eb647b07e16e4d502f30ee026a Mon Sep 17 00:00:00 2001 From: Michael Yong Date: Fri, 11 Sep 2026 15:15:12 -0700 Subject: [PATCH] fix: restore stdin EOF for environment hooks --- .../src/environment-lifecycle-script.test.ts | 25 +++++++++++++++++++ .../src/environment-lifecycle-script.ts | 4 +-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/host-daemon/src/environment-lifecycle-script.test.ts b/apps/host-daemon/src/environment-lifecycle-script.test.ts index 47efe2686d..bb73e297b2 100644 --- a/apps/host-daemon/src/environment-lifecycle-script.test.ts +++ b/apps/host-daemon/src/environment-lifecycle-script.test.ts @@ -30,6 +30,31 @@ afterEach(async () => { }); describe("core environment scripts", () => { + it.each(["setup", "teardown"] as const)( + "supplies stdin EOF so %s continues to completion", + async (kind) => { + const workspacePath = await workspace( + kind, + 'cat >/dev/null\nprintf complete > marker\nprintf "\\342"\nsleep 0.05\nprintf "\\234\\223\\n"\nprintf "done\\n" >&2\n', + ); + const output: string[] = []; + const run = kind === "setup" ? runSetupScript : runTeardownScript; + const result = await run({ + workspacePath, + timeoutMs: 1000, + env: { PATH: "/usr/bin:/bin" }, + onProgress: (entry) => output.push(entry.text), + }); + expect(result).toMatchObject({ ran: true, exitCode: 0 }); + expect(await readFile(join(workspacePath, "marker"), "utf8")).toBe( + "complete", + ); + expect(output).toContain("✓"); + expect(output).toContain("done"); + expect(result.output).toContain("✓\n"); + }, + ); + it("runs in the environment directory and streams stdout and stderr", async () => { const workspacePath = await workspace( "setup", diff --git a/apps/host-daemon/src/environment-lifecycle-script.ts b/apps/host-daemon/src/environment-lifecycle-script.ts index 24e3123ec6..4637a625b9 100644 --- a/apps/host-daemon/src/environment-lifecycle-script.ts +++ b/apps/host-daemon/src/environment-lifecycle-script.ts @@ -4,7 +4,7 @@ import type { HostDaemonContributedEnvEntry } from "@bb/host-daemon-contract"; import { isProcessGroupAlive, killProcessGroup, - spawnPortablePipedProcess, + spawnPortableOutputProcess, supportsProcessGroups, } from "@bb/process-utils"; import fs from "node:fs/promises"; @@ -133,7 +133,7 @@ async function runLifecycleScript( }, true, ); - const child = spawnPortablePipedProcess({ + const child = spawnPortableOutputProcess({ command: command.command, args: command.args, cwd: args.workspacePath,