Skip to content

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

Description

@alove20

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions