From 28e1334fab57002e6fc46a26825491418e2b9f61 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 17 Apr 2026 05:53:53 +0800 Subject: [PATCH] fix: SIGTERM/SIGINT shutdown exit code regression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Node's signal listeners receive the signal name ('SIGTERM' / 'SIGINT') as the first argument. When shutdown() started accepting an optional exitCode parameter in the prior disconnect-cleanup commit, the bare `process.on('SIGTERM', shutdown)` registration started silently calling shutdown('SIGTERM'). The string passed through to process.exit(), Node coerced it to NaN, and the process exited with code 1 instead of 0. Wrap both listeners so they call shutdown() with no args — signal name never leaks into the exitCode slot. Surfaced by /ship's adversarial subagent. Co-Authored-By: Claude Opus 4.7 (1M context) --- browse/src/server.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/browse/src/server.ts b/browse/src/server.ts index 81548c1b..d25fc8fa 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -1241,8 +1241,11 @@ async function shutdown(exitCode: number = 0) { } // Handle signals -process.on('SIGTERM', shutdown); -process.on('SIGINT', shutdown); +// Node passes the signal name (e.g. 'SIGTERM') as the first arg to listeners. +// Wrap so shutdown() receives no args — otherwise the string gets passed as +// exitCode and process.exit() coerces it to NaN, exiting with code 1 instead of 0. +process.on('SIGTERM', () => shutdown()); +process.on('SIGINT', () => shutdown()); // Windows: taskkill /F bypasses SIGTERM, but 'exit' fires for some shutdown paths. // Defense-in-depth — primary cleanup is the CLI's stale-state detection via health check. if (process.platform === 'win32') {