fix(cli): retain SIGPIPE status through dispatch cleanup

This commit is contained in:
morluto
2026-10-24 07:32:40 +08:00
parent 36f26a1bfe
commit d421e3eeef
2 changed files with 31 additions and 1 deletions
+10 -1
View File
@@ -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. */
+21
View File
@@ -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",