[BUG] MCP server and subagent processes not cleaned up on session end — orphan accumulation (macOS PPID=1)
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 execMCP 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 execcreate 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:
- All tmux workers survive as orphans (PPID=1)
- Stop hooks do NOT fire for background agents (#25147)
- This multiplies the leak: N workers × M MCP servers per worker
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
- #20369 — Orphaned subagent process leaks memory
- #22612 — MCP servers not cleaned up when sessions end
- #26658 — Orphan accumulation causes OOM (closed as duplicate)
- #25147 — Background agents bypass Stop hooks
---
Reported with Claude Code + OMC v4.6.0 on macOS M4 Max
This issue has 11 comments on GitHub. Read the full discussion on GitHub ↗