fix(coding-agent): bypassed extension guard for shutdown
- Captured the native hard-exit function for postmortem signal, fatal, and manual exits. - Added bounded child-process coverage for SIGINT and fatal cleanup during pending guarded loads. - Preserved extension and hook exit isolation and made the EPIPE race assertion observe the real exit event. Fixes #6488
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed postmortem signal and fatal shutdown exits being intercepted by temporary `process.exit` guards during extension startup ([#6488](https://github.com/can1357/oh-my-pi/issues/6488)).
|
||||
|
||||
## [17.0.9] - 2026-07-23
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -29,6 +29,8 @@ const callbackList: ((reason: Reason) => Promise<void> | void)[] = [];
|
||||
// Tracks cleanup run state (to prevent recursion/reentry issues)
|
||||
let cleanupStage: "idle" | "running" | "complete" = "idle";
|
||||
const CLEANUP_DEADLINE_MS = 10_000;
|
||||
const exitProcess =
|
||||
typeof process.reallyExit === "function" ? process.reallyExit.bind(process) : process.exit.bind(process);
|
||||
let cleanupPromise: Promise<void> | undefined;
|
||||
let stdioDisconnectRegistrations = 0;
|
||||
|
||||
@@ -177,7 +179,7 @@ function formatFatalError(label: string, err: Error): string {
|
||||
}
|
||||
|
||||
async function exitAfterFatal(label: string, logMessage: string, err: Error, reason: Reason): Promise<void> {
|
||||
const forcedExit = setTimeout(() => process.exit(1), CLEANUP_DEADLINE_MS);
|
||||
const forcedExit = setTimeout(() => exitProcess(1), CLEANUP_DEADLINE_MS);
|
||||
try {
|
||||
restoreTerminalStderr();
|
||||
// A revoked terminal can make stream writes raise another fatal error. Use
|
||||
@@ -189,7 +191,7 @@ async function exitAfterFatal(label: string, logMessage: string, err: Error, rea
|
||||
await runCleanup(reason);
|
||||
} finally {
|
||||
clearTimeout(forcedExit);
|
||||
process.exit(1);
|
||||
exitProcess(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +199,7 @@ if (isMainThread) {
|
||||
process
|
||||
.on("SIGINT", async () => {
|
||||
await runCleanup(Reason.SIGINT);
|
||||
process.exit(130); // 128 + SIGINT (2)
|
||||
exitProcess(130); // 128 + SIGINT (2)
|
||||
})
|
||||
.on("SIGUSR1", () => {
|
||||
if (inspectorOpened) return;
|
||||
@@ -254,11 +256,11 @@ if (isMainThread) {
|
||||
})
|
||||
.on("SIGTERM", async () => {
|
||||
await runCleanup(Reason.SIGTERM);
|
||||
process.exit(143); // 128 + SIGTERM (15)
|
||||
exitProcess(143); // 128 + SIGTERM (15)
|
||||
})
|
||||
.on("SIGHUP", async () => {
|
||||
await runCleanup(Reason.SIGHUP);
|
||||
process.exit(129); // 128 + SIGHUP (1)
|
||||
exitProcess(129); // 128 + SIGHUP (1)
|
||||
});
|
||||
} else {
|
||||
// Worker thread: only register exit handler for cleanup.
|
||||
@@ -341,5 +343,5 @@ export async function quit(code: number = 0): Promise<void> {
|
||||
process.stdout.once("drain", resolve);
|
||||
await Promise.race([promise, Bun.sleep(5000)]);
|
||||
}
|
||||
process.exit(code);
|
||||
exitProcess(code);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import * as fs from "node:fs";
|
||||
import { postmortem } from "@oh-my-pi/pi-utils";
|
||||
|
||||
const childFlag = "--stdio-epipe-child";
|
||||
@@ -21,15 +22,9 @@ if (childFlagIndex >= 0) {
|
||||
const marker = process.argv[process.argv.indexOf(raceChildFlag) + 1];
|
||||
if (!marker) throw new Error("Missing cleanup marker path");
|
||||
let cleanupComplete = false;
|
||||
let exitAttempted = false;
|
||||
const exit = process.exit;
|
||||
process.exit = ((code?: number) => {
|
||||
if (!exitAttempted) {
|
||||
exitAttempted = true;
|
||||
void Bun.write(marker, cleanupComplete ? "after cleanup" : "before cleanup").then(() => exit(code));
|
||||
}
|
||||
return undefined as never;
|
||||
}) as typeof process.exit;
|
||||
process.on("exit", () => {
|
||||
fs.writeFileSync(marker, cleanupComplete ? "after cleanup" : "before cleanup");
|
||||
});
|
||||
postmortem.registerStdioDisconnectHandling();
|
||||
postmortem.register("stdio-epipe-race-test", async () => {
|
||||
process.stderr.write("cleanup started\n");
|
||||
|
||||
Reference in New Issue
Block a user