fix: SIGTERM/SIGINT shutdown exit code regression

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) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-04-17 05:53:53 +08:00
parent b4987c870a
commit 28e1334fab
+5 -2
View File
@@ -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') {