Summary
Claude Code does not terminate MCP server child processes or subagent processes when a session ends (normal exit, crash, or terminal close). On macOS, these become orphans with PPID=1 (adopted by launchd) and accumulate indefinitely.
This is a follow-up to #20369, #22612, #26658 with concrete reproduction data and a community workaround that confirms the root cause.
Environment
- Claude Code: 2.1.72+
- OS: macOS 26.3.1 (Darwin, Apple M4 Max)
- MCP servers: context7, supermemory, serena, playwright, greptile, typescript-lsp, dev-browser, + ~15 others
Observed behavior
After a typical workday with multiple Claude Code sessions:
- 107 unsigned node processes (PPID=1) — orphaned MCP servers
- 45 signed node processes (still active)
- Combined: ~7.75 GB RAM, ~40% CPU wasted
- Each
npm exec MCP wrapper spawns 2 node processes, neither terminated on exit
Verified via: ps -eo pid,ppid,etimes,rss,comm | awk '$2==1'
Root cause (confirmed)
- macOS lacks
prctl(PR_SET_PDEATHSIG) — no native way to auto-kill children when parent dies
- Claude Code does not track MCP server PIDs for cleanup on exit
- MCP servers spawned via
npm exec create 2 processes each (wrapper + node child), neither registered
- SIGHUP is not forwarded to children on terminal close
The getOrCreateMcpConnection memoized function (identified in #22612) creates-instead-of-gets on disconnect, spawning duplicate servers and leaking PIDs.
Impact with OMC / Team mode
When using OMC (oh-my-claudecode) team N:role mode, each worker is a claude process spawned in a tmux pane. When the parent session dies:
Community workarounds (confirmed working)
Three independent community projects have converged on the same solution:
- theQuert/cc-reaper: PGID group kill (Stop hook) + PPID=1 LaunchAgent daemon
- NathanSkene's claude-cleanup gist: PPID=1 + age guard + tree kill
- ImL1s/clean-orphans: PPID=1 + pattern whitelist
All use kill -- -$PGID (group kill) as primary method, PPID=1 scan as fallback.
Requested fix
Minimal (Stop hook support in core)
When Claude Code exits (any reason), send SIGTERM to its own process group:
process.on('exit', () => {
try { process.kill(-process.getpgid(process.pid), 'SIGTERM'); } catch {}
});
Proper fix
- Track spawned MCP server PIDs at session start
- On session end (all exit paths including SIGHUP), iterate and SIGTERM each tracked PID
- Use
setpgrp() on spawned MCP processes to put them in the same process group
- Add startup cleanup: detect MCP orphans from dead sessions (PPID=1 check)
Fix detection note
The community is monitoring CHANGELOG.md for keywords: orphan, PPID, process group, MCP.*clean, session.*cleanup to detect when this is addressed.
Related issues
Reported with Claude Code + OMC v4.6.0 on macOS M4 Max
Summary
Claude Code does not terminate MCP server child processes or subagent processes when a session ends (normal exit, crash, or terminal close). On macOS, these become orphans with PPID=1 (adopted by launchd) and accumulate indefinitely.
This is a follow-up to #20369, #22612, #26658 with concrete reproduction data and a community workaround that confirms the root cause.
Environment
Observed behavior
After a typical workday with multiple Claude Code sessions:
npm execMCP wrapper spawns 2 node processes, neither terminated on exitVerified via:
ps -eo pid,ppid,etimes,rss,comm | awk '$2==1'Root cause (confirmed)
prctl(PR_SET_PDEATHSIG)— no native way to auto-kill children when parent diesnpm execcreate 2 processes each (wrapper + node child), neither registeredThe
getOrCreateMcpConnectionmemoized function (identified in #22612) creates-instead-of-gets on disconnect, spawning duplicate servers and leaking PIDs.Impact with OMC / Team mode
When using OMC (oh-my-claudecode)
team N:rolemode, each worker is aclaudeprocess spawned in a tmux pane. When the parent session dies:Community workarounds (confirmed working)
Three independent community projects have converged on the same solution:
All use
kill -- -$PGID(group kill) as primary method, PPID=1 scan as fallback.Requested fix
Minimal (Stop hook support in core)
When Claude Code exits (any reason), send SIGTERM to its own process group:
Proper fix
setpgrp()on spawned MCP processes to put them in the same process groupFix detection note
The community is monitoring
CHANGELOG.mdfor keywords:orphan,PPID,process group,MCP.*clean,session.*cleanupto detect when this is addressed.Related issues
Reported with Claude Code + OMC v4.6.0 on macOS M4 Max