Automated cleanup for orphan Claude Code processes (subagents, MCP servers, plugins) that leak memory after sessions end.
Claude Code spawns subagent processes and MCP servers for each session. When sessions end (especially abnormally), these processes become orphans (PPID=1) and keep consuming RAM and CPU — often 200-400 MB each, with some (like Cloudflare's MCP server) hitting 550%+ CPU. With multiple sessions over a day, this can accumulate to 7+ GB of wasted memory.
This is a widely reported issue affecting macOS and Linux users.
| Process Type | Pattern | Typical Size |
|---|---|---|
| Subagents | claude --output-format stream-json |
180-300 MB each |
| MCP servers (short-lived) | npx mcp-server-cloudflare, npm exec mcp-*, etc. |
40-110 MB each |
| claude-mem worker | worker-service.cjs --daemon (bun) |
100 MB |
| File descriptors | VM processes, settings.json, MCP stdio pipes | ~6,200 FDs/hr leak rate |
Not killed: Long-running MCP servers shared across sessions (Supabase, Stripe, context7, claude-mem, chroma-mcp) are whitelisted across all cleanup layers — including PGID group kills. When a session ends, its stop hook and
claude-guardskip whitelisted MCP servers so other sessions can continue using them.
All layers use PGID-based process group cleanup as the primary method — killing entire process groups spawned by a Claude session with a single kill -- -$PGID. Pattern-based detection is kept as a fallback for edge cases.
Session ends normally
└── Stop hook — kills session's process group via PGID (catches all children)
Session crashes / terminal force-closed
└── proc-janitor daemon — scans every 30s, kills orphans after 60s grace
└── OR: LaunchAgent — zero-dependency macOS native, PGID group kill + PPID=1 fallback
Manual intervention needed
└── claude-cleanup — finds orphaned PGIDs (leader has PPID=1), kills entire groups
└── claude-ram — check RAM/CPU usage breakdown with orphan visibility
Claude Code sessions are process group leaders (PGID = session PID). All spawned MCP servers, subagents, and their children inherit this PGID. This means one kill -- -$PGID reliably cleans up everything — including third-party MCP servers that pattern matching might miss.
Safety: PGID cleanup only targets groups whose leader is a Claude CLI session (claude.*stream-json). It never matches by group membership — other apps like Chrome and Cursor have claude subprocesses in their process groups, so matching by membership would kill them.
git clone https://github.com/theQuert/cc-reaper.git
cd cc-reaper
chmod +x install.sh
./install.shUpdating:
git pull
./install.shThe installer auto-updates hook and shell functions. For proc-janitor users, manually sync the config:
cp proc-janitor/config.toml ~/.config/proc-janitor/config.toml
# Edit the log path: replace ~ with your actual home directoryAdd to ~/.zshrc or ~/.bashrc:
source /path/to/cc-reaper/shell/claude-cleanup.shCommands available after restart:
claude-ram— show RAM/CPU usage breakdown with per-session details and orphan visibility (read-only)claude-fd— show file descriptor usage per session and VirtualMachine processes (read-only)claude-sessions— list all active sessions with idle detection and process tree RAMclaude-cleanup— kill orphan processes immediately (PGID group kill + pattern fallback)claude-guard— automatic session reaper: kills FD-leaking, bloated (RSS > threshold), and excess idle sessionsclaude-guard --dry-run— preview what claude-guard would kill without actually killing
Copy the hook script:
mkdir -p ~/.claude/hooks
cp hooks/stop-cleanup-orphans.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/stop-cleanup-orphans.shAdd to ~/.claude/settings.json in the "Stop" hooks array:
{
"type": "command",
"command": "\"$HOME\"/.claude/hooks/stop-cleanup-orphans.sh",
"timeout": 15
}Full settings.json example
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$HOME\"/.claude/hooks/stop-cleanup-orphans.sh",
"timeout": 15
}
]
}
]
}
}Native macOS approach — no Homebrew or Rust required. Runs every 10 minutes, detects orphans by PPID=1.
mkdir -p ~/.cc-reaper/logs
cp launchd/cc-reaper-monitor.sh ~/.cc-reaper/
chmod +x ~/.cc-reaper/cc-reaper-monitor.sh
# Install and replace __HOME__ with actual path
sed "s|__HOME__|$HOME|g" launchd/com.cc-reaper.orphan-monitor.plist \
> ~/Library/LaunchAgents/com.cc-reaper.orphan-monitor.plist
launchctl load ~/Library/LaunchAgents/com.cc-reaper.orphan-monitor.plistUseful commands:
launchctl list | grep cc-reaper # check if running
cat ~/.cc-reaper/logs/monitor.log # view cleanup log
launchctl unload ~/Library/LaunchAgents/com.cc-reaper.orphan-monitor.plist # stopRust-based daemon with grace period, whitelist, and detailed logging. Requires Homebrew or Cargo.
# Install
brew install jhlee0409/tap/proc-janitor # or: cargo install proc-janitor
# Copy config
mkdir -p ~/.config/proc-janitor
cp proc-janitor/config.toml ~/.config/proc-janitor/config.toml
chmod 600 ~/.config/proc-janitor/config.tomlEdit ~/.config/proc-janitor/config.toml and replace ~ in the log path with your actual home directory.
Start daemon:
brew services start jhlee0409/tap/proc-janitor # auto-start on boot
proc-janitor start # or manualUseful commands:
proc-janitor scan # dry run — show orphans without killing
proc-janitor clean # kill detected orphans
proc-janitor status # check daemon healthclaude-guard is an automatic session reaper that prevents runaway resource consumption. It operates in three phases:
- FD-leak session kill — Sessions whose open file descriptor count exceeds
CC_MAX_FDare killed immediately. This addresses the widely reported FD exhaustion issue where VM processes leak ~6,200 FDs/hour, eventually causing system-wide "Operation not permitted" errors. - Bloated session kill — Sessions whose tree RSS (process + all children) exceeds
CC_MAX_RSS_MBare killed immediately via PGID, regardless of whether they're idle or active. This addresses the ~42 GB/hr memory leak caused by unreleased streaming ArrayBuffers. - Idle session eviction — If session count still exceeds
CC_MAX_SESSIONS, the oldest idle sessions are killed.
claude-guard # run the guard (kills bloated + excess idle)
claude-guard --dry-run # preview without killing| Variable | Default | Description |
|---|---|---|
CC_MAX_SESSIONS |
3 | Max allowed concurrent sessions before idle eviction |
CC_IDLE_THRESHOLD |
1 | CPU% below which a session is considered idle |
CC_MAX_RSS_MB |
4096 | Tree RSS threshold (MB); sessions exceeding this are killed regardless of activity |
CC_MAX_FD |
10000 | File descriptor threshold; sessions exceeding this are killed as FD-leak |
Example: lower the thresholds for constrained machines:
export CC_MAX_RSS_MB=2048
export CC_MAX_FD=5000
claude-guard| Tool | Required | Install |
|---|---|---|
| bash/zsh | Required | Pre-installed on macOS/Linux |
| macOS LaunchAgent | Option A (recommended) | Built-in, zero dependencies |
| proc-janitor | Option B | brew install jhlee0409/tap/proc-janitor |
| Claude Code | — | The tool this project cleans up after |
cc-reaper/
├── install.sh # One-command installer/updater (interactive daemon choice)
├── hooks/
│ └── stop-cleanup-orphans.sh # Claude Code Stop hook (PGID + pattern fallback)
├── launchd/
│ ├── cc-reaper-monitor.sh # LaunchAgent monitor script (PGID + PPID=1 fallback)
│ └── com.cc-reaper.orphan-monitor.plist # LaunchAgent config (10-min interval)
├── proc-janitor/
│ └── config.toml # proc-janitor daemon config (alternative to LaunchAgent)
├── shell/
│ └── claude-cleanup.sh # Shell functions (claude-ram, claude-fd, claude-cleanup, claude-sessions, claude-guard)
└── README.md
See CHANGELOG.md for version history.
- anthropics/claude-code#20369 — Orphaned subagent process leaks memory
- anthropics/claude-code#22554 — Subagent processes not terminating on macOS
- anthropics/claude-code#25545 — Excessive RAM when idle
- thedotmack/claude-mem#650 — worker-service spawns subagents that don't exit
- anthropics/claude-code#29888 — VM process FD leak (~6,200/hr)
- anthropics/claude-code#28896 — settings.json FD leak (1 per tool call)
- anthropics/claude-code#37482 — MCP server stdio pipe breaks (orphaned FDs)
Apache 2.0