fix(lifecycle): detect parent death on Windows via PID existence probe (#982) - #987
fix(lifecycle): detect parent death on Windows via PID existence probe (#982)#987alove20 wants to merge 1 commit into
Conversation
mksglu#982) startLifecycleGuard already runs everywhere, but makeDefaultIsParentAlive only detected death via the ppid changing (POSIX reparent-to-init) or the grandparent going to PID 1 (POSIX ps probe, NaN on Windows). On Windows an orphaned child keeps its original ppid and is never reparented, so isParentAlive() returned true forever and the orphaned server never shut down. Add a cross-platform positive existence probe (process.kill(ppid, 0)) as an additional death signal; it is injectable for testing and strictly additive to the existing POSIX paths.
|
Framing note for reviewers: this extends the zombie/orphan-elimination strategy that ADR 0001 already relies on, to Windows. ADR 0001 keeps the content store intentionally multi-writer and identifies the real cause of WAL bloat + |
Fixes #982.
startLifecycleGuardruns on every platform (server.ts), butmakeDefaultIsParentAlive(src/lifecycle.ts) only recognizes parent death two ways: the direct ppid changing (POSIX reparent-to-init), or the grandparent becoming PID 1 (apsprobe that returnsNaNon Windows).On Windows neither fires. A child is not reparented when its parent dies —
process.ppidkeeps returning the original (now-dead) value — so the equality check stays green and thepsgrandparent probe is unavailable. Result:isParentAlive()returnstrueforever, the guard never callsonShutdown, and the MCP server orphans and lingers after the Claude Code session ends/crashes/reboots (pre-existing busy-wait then pinned a core — see #985).Change: add a positive PID existence probe via
process.kill(ppid, 0)(ESRCH⇒ gone,EPERM⇒ alive). This is the one death signal that works cross-platform, and it's strictly additive — POSIX keeps its existing faster paths. The probe is injectable (isPidAlive) for deterministic tests.Adds a
#982test proving a stable-ppid + NaN-grandparent instance is reaped once the parent PID disappears, and threads the injected probe through the existing #311 grandparent tests. Full build + lifecycle/stdin/pi-bridge suites pass.