From d421e3eeef5bf52fe9bcd88d228883629eb2b16e Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:32:40 +0800 Subject: [PATCH] fix(cli): retain SIGPIPE status through dispatch cleanup --- scripts/rea.mjs | 11 ++++++++++- tests/boundary/cli/dispatcher.test.ts | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/scripts/rea.mjs b/scripts/rea.mjs index 25711b3142..cb97056f8e 100755 --- a/scripts/rea.mjs +++ b/scripts/rea.mjs @@ -205,6 +205,10 @@ if (!(await compiledRuntimeExists(runtimeFiles))) { ); } } catch (cause) { + if (cause?.code === "EPIPE") { + stdoutClosed = true; + process.exitCode = SIGPIPE_EXIT_STATUS; + } dispatchFailure = cause; } finally { // Serve and output rendering can throw after a worker is parked. Stop it @@ -224,7 +228,12 @@ if (!(await compiledRuntimeExists(runtimeFiles))) { } } } - if (!exiting && dispatchFailure !== undefined) throw dispatchFailure; + if ( + !exiting && + dispatchFailure !== undefined && + !(stdoutClosed && dispatchFailure?.code === "EPIPE") + ) + throw dispatchFailure; } /** True when the first positional command is `doctor`, after global output flags. */ diff --git a/tests/boundary/cli/dispatcher.test.ts b/tests/boundary/cli/dispatcher.test.ts index 9cbd1b37a3..93e4dba95c 100644 --- a/tests/boundary/cli/dispatcher.test.ts +++ b/tests/boundary/cli/dispatcher.test.ts @@ -231,6 +231,27 @@ describe("executable dispatcher", () => { }); describe("executable dispatcher JavaScript shutdown", () => { + workspaceCliTest.for([false, true])( + "handles a thrown pipe error while preserving mixed cleanup failure: %s", + async (cleanupFails, { processes, workspace }) => { + await installCliRuntime( + workspace, + `export const closeCliJavaScriptResources = async () => { ${cleanupFails ? 'throw new Error("worker still owned");' : ""} }; export const createCli = () => ({ serve: () => { throw Object.assign(new Error("broken pipe"), { code: "EPIPE" }); } });\n`, + ); + const result = await processes.run( + process.execPath, + [workspace.path("scripts/rea.mjs"), "fixture"], + { cwd: workspace.root }, + ); + expect(result.exitCode).toBe(cleanupFails ? 1 : 141); + expect(result.stdout).toBe(""); + if (cleanupFails) { + expect(result.stderr).toContain("broken pipe"); + expect(result.stderr).toContain("worker still owned"); + } else expect(result.stderr).toBe(""); + }, + ); + workspaceCliTest.for([ "success", "cleanup-failure",