Windows: MCP server child orphans & CPU-spins on session end (parent-death guard is Linux/Bun-only)
Summary
On Windows, the context-mode MCP server child process is not reaped when its client (Claude Code / claude.exe) session ends, crashes, or the machine reboots. The orphan survives and busy-loops, accumulating unbounded CPU (hundreds to thousands of CPU-seconds) while holding 170–490 MB RSS. Healthy instances sit at ~1–3 CPU-seconds, so a hung one is unmistakable. Over a multi-session workday the strays pile up, hang ctx_* calls, and contribute to system-wide memory/CPU pressure.
Root cause
The parent-death guard already exists — but it never runs on Windows.
start.mjs (v1.0.169, lines ~62–135, added for issue #862) forwards stdin-EOF → SIGTERM → SIGKILL to the server child, which is exactly what prevents the orphan. But the entire block is gated behind:
if (typeof globalThis.Bun === "undefined" && process.platform === "linux") { ... }plus a discovered Bun binary. On Windows the MCP server runs as a bare node …\cli.bundle.mjs child with no process.stdin.on('end'|'close') exit handler and no parent-liveness (ppid) poll. When the parent goes away ungracefully, nothing tells the child to exit, so it orphans and pins a core.
(Windows also does not cascade-kill children on parent death unless the launcher uses a Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, so the server needs its own guard for hard exits.)
Environment
- Windows 11
- Claude Code desktop host
2.1.209 - context-mode plugin v1.0.169
Evidence (observed orphans, all node …\context-mode\cli.bundle.mjs)
| Started | CPU-seconds | RSS |
|---|---|---|
| prior day | ~4,323 | — |
| — | ~789 | 485 MB |
| — | ~778 | 299 MB |
| — | ~7,177 | 173 MB |
| — | ~628 | 184 MB |
Healthy instances at the same times: ~1–3 CPU-seconds. Killing a stray immediately restored ctx_* responsiveness each time. Every live child had a claude.exe parent (one per active session); the leak is that ended sessions' children persist.
The sibling token-savior indexer shows the same "per-session helper not reaped" shape (seen at 145 / 116 CPU-s) — likely the same class of bug.
Suggested fix
Ungate the #862 guard so it also runs on the Windows/node boot path. At minimum, at server startup:
process.stdin.on('end', () => process.exit(0));
process.stdin.on('close', () => process.exit(0));
const ppid = process.ppid;
setInterval(() => { try { process.kill(ppid, 0); } catch { process.exit(0); } }, 15000).unref();and ensure the MCP transport's onclose/close tears the process down, with any keep-alive setInterval/reconnect loop .unref()'d and backed off. The stdin handlers are passive (they don't consume transport data) and safe to add alongside StdioServerTransport; the .unref()'d ppid poll is the belt-and-suspenders for hard exit / reboot where stdin EOF may not arrive.
Source: mksglu/context-mode