Skip to content

fix(lifecycle): detect parent death on Windows via PID existence probe (#982) - #987

Open
alove20 wants to merge 1 commit into
mksglu:mainfrom
alove20:fix/982-windows-parent-death-probe
Open

fix(lifecycle): detect parent death on Windows via PID existence probe (#982)#987
alove20 wants to merge 1 commit into
mksglu:mainfrom
alove20:fix/982-windows-parent-death-probe

Conversation

@alove20

@alove20 alove20 commented Jul 22, 2026

Copy link
Copy Markdown

Fixes #982.

startLifecycleGuard runs on every platform (server.ts), but makeDefaultIsParentAlive (src/lifecycle.ts) only recognizes parent death two ways: the direct ppid changing (POSIX reparent-to-init), or the grandparent becoming PID 1 (a ps probe that returns NaN on Windows).

On Windows neither fires. A child is not reparented when its parent dies — process.ppid keeps returning the original (now-dead) value — so the equality check stays green and the ps grandparent probe is unavailable. Result: isParentAlive() returns true forever, the guard never calls onShutdown, 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 #982 test 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.

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.
@alove20

alove20 commented Jul 22, 2026

Copy link
Copy Markdown
Author

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 + ctx_search hangs (#560) as processes that never exit gracefully — so closeDB()'s wal_checkpoint(TRUNCATE) never fires and a lingering reader lock starves other checkpoints. On POSIX that zombie is eliminated by reparent-to-init detection (ppid change / grandparent→PID 1). On Windows neither signal exists: an orphaned child keeps its original ppid and readGrandparentPpid returns NaN, so makeDefaultIsParentAlive() returns true forever and the guard never reaps. This PR adds the one cross-platform death signal (process.kill(ppid, 0)), so the ADR's graceful-exit assumption finally holds on Windows too. No locking is introduced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: MCP server child orphans & CPU-spins on session end (parent-death guard is Linux/Bun-only)

1 participant