Skip to content

[BUG] MCP server and subagent processes not cleaned up on session end — orphan accumulation (macOS PPID=1) #33947

@rolldav

Description

@rolldav

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)

  1. macOS lacks prctl(PR_SET_PDEATHSIG) — no native way to auto-kill children when parent dies
  2. Claude Code does not track MCP server PIDs for cleanup on exit
  3. MCP servers spawned via npm exec create 2 processes each (wrapper + node child), neither registered
  4. 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

  1. Track spawned MCP server PIDs at session start
  2. On session end (all exit paths including SIGHUP), iterate and SIGTERM each tracked PID
  3. Use setpgrp() on spawned MCP processes to put them in the same process group
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:mcpbugSomething isn't workinghas reproHas detailed reproduction stepsplatform:macosIssue specifically occurs on macOS

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions