8b10770dc8
The dev-server shutdown path had two teardown bugs: 1. The crash-respawn setInterval was never cleared. During shutdown's async window (await pm.shutdown(); await server.close()), the 500ms interval kept firing; if the child had crashed it could call pm.spawn() and start a BRAND-NEW child while the parent was tearing down — orphaning that child when the parent exits. 2. shutdown was registered directly as the SIGINT/SIGTERM handler with no re-entrancy guard. Pressing Ctrl+C twice (common when shutdown feels slow) invoked shutdown() twice concurrently → double pm.shutdown()/server.close() and two racing process.exit(0). Fix: extract a testable createShutdown(deps) factory that owns a shuttingDown flag (2nd+ invocation is a no-op) and clears the respawn interval FIRST, before the async teardown window. The respawn-tick body is extracted into respawnTick(deps), which re-checks isShuttingDown both before and AFTER the extractBundle await — closing the in-flight window where a tick already mid-extraction could still spawn after clearInterval. Both helpers are module-scoped (not re-exported from the package public index.ts). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>