Orphaned subagent process leaks memory when parent terminal session terminated

Status Closed — not planned
Reported on v2.1.17
Maintainer reply None cached
Activity 15 comments · opened Jan 23, 2026 · closed May 28, 2026

Description

When a parent Claude Code terminal session is terminated (e.g., VS Code integrated terminal closed/killed), subagents spawned via the Task tool are not properly cleaned up. The orphaned subagent process accumulates massive memory (30GB in this case) and must be manually killed.

Steps to Reproduce

  1. Run Claude Code in VS Code integrated terminal
  2. Spawn a subagent using the Task tool (in this case, n8n-workflow subagent to monitor an ongoing execution)
  3. Parent session freezes or becomes unresponsive
  4. Terminate the VS Code terminal
  5. Subagent process is orphaned (reparented to launchd/PID 1 on macOS)

Observed Behavior

  • Subagent process continues running detached
  • Process accumulates ~30GB memory in WebKit Malloc (mostly swapped to disk)
  • Process state shows as uninterruptible wait (U)
  • Regular kill doesn't work; requires kill -9
  • Briefly becomes zombie before cleanup

Memory Profile (from vmmap -summary)

Physical footprint:         30.8G
Physical footprint (peak):  30.8G

WebKit Malloc               37.0G virtual   708.9M resident   29.8G swapped

Expected Behavior

  • Subagent processes should be terminated when parent session exits
  • If orphaned, processes should not leak memory indefinitely
  • Graceful signal handling (SIGTERM should work)

Environment

  • Claude Code version: 2.1.17
  • Platform: macOS 26.2 (Darwin 25.2.0, ARM64)
  • Terminal: VS Code integrated terminal
  • Subagent type: n8n-workflow

Additional Context

Process details before kill:

PID    PPID  RSS       VSZ        COMMAND
73002  1     479984    538806112  claude

Parent PID 1 (launchd) confirms orphaned state.

View original on GitHub ↗

15 Comments

github-actions[bot] · 7 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/19045
  2. https://github.com/anthropics/claude-code/issues/17391
  3. https://github.com/anthropics/claude-code/issues/19926

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

madshn · 7 months ago

Additional Trigger: Active Session Freeze During Skill/MCP Call

Found another variant of this memory leak that affects active sessions (not orphaned subagents).

Trigger Scenario

Claude session freezes when initiating a Skill tool that calls an MCP server:

⏺ supabase-db(Check for missing links)
  ⎿  
     Skill(supabase-db)
     +1 more tool use (ctrl+o to expand)

✻ Sublimating… (8m 17s)

The session was invoking a Supabase MCP skill when it entered uninterruptible state.

Process State

PID   PPID   RSS       %MEM  STATE  ELAPSED  
75663 75548  1,332,864  7.9%  U+     13:41
  • Not orphaned - parent is active zsh (PPID 75548)
  • State U+ - uninterruptible wait (same as orphan case)
  • Age: 14 minutes - leaked 35GB in under 15 minutes

Memory Profile (vmmap)

Physical footprint:         35.7G
Physical footprint (peak):  35.7G

WebKit Malloc   41.9G virtual   494.8M resident   34.9G swapped

Same WebKit Malloc leak pattern as the orphan case.

MCP Child Processes (all healthy)

75799 - npm exec @upstash/context7-mcp (9MB, S+)
75800 - npm exec @playwright/mcp@latest (9MB, S+)  
76007 - npm exec @upstash/context7-mcp@latest (9MB, S+)
76008 - claude-in-chrome-mcp (17MB, S+)

The MCP children are fine - the leak is in the parent Claude process.

Key Differences from Orphan Case

| Attribute | Issue #20369 (Orphan) | This Case (Active) |
|-----------|----------------------|-------------------|
| Process type | Spawned subagent | Main session |
| Parent | Reparented to PID 1 | Active zsh |
| Trigger | Parent termination | Skill/MCP call |
| Memory pattern | Same | Same |

Conclusion

The WebKit Malloc leak can be triggered by:

  1. Orphaned subagents (original report)
  2. Active sessions during MCP/Skill operations (this case)

Both result in the same uninterruptible state and runaway memory consumption.

Environment: Claude Code 2.1.17, macOS 26.2 (Darwin 25.2.0, ARM64)

chenyanchen · 7 months ago

Same issue after close all terminal.

CPU screenshot:
<img width="948" height="24" alt="Image" src="https://github.com/user-attachments/assets/5eba92c8-30b1-440a-b33b-bc40e6cc7879" />

Memory screenshot:
<img width="948" height="109" alt="Image" src="https://github.com/user-attachments/assets/2f419830-b4ee-4119-b26f-f8493878b9f6" />

dizhaky · 7 months ago

Additional data point: 392 orphaned processes over ~5 days

I experienced a more severe accumulation of this bug - 392 orphaned node subagent processes accumulated over approximately 5 days of usage with Claude Desktop.

Reproduction context

  • Client: Claude Desktop (not VS Code terminal)
  • Claude Code version: 2.1.15
  • Platform: macOS (Darwin 25.3.0, ARM64)
  • Project directory: Working primarily in one repository

Evidence

All 392 processes had their cwd set to the project directory and held open file handles to .claude/CLAUDE.md and .claude/settings.local.json:

lsof +D /Users/.../cloud-admin-toolkit 2>/dev/null | grep "^node" | wc -l
392

Sample from lsof output showing the pattern:

node    6967 danizhaky  cwd       DIR  /Users/.../cloud-admin-toolkit
node    6967 danizhaky   39r      REG  /Users/.../cloud-admin-toolkit/.claude/CLAUDE.md
node    6967 danizhaky   40r      REG  /Users/.../cloud-admin-toolkit/.claude/settings.local.json

This pattern repeated for all 392 processes. Many appeared to be observer/monitoring subagents with --disallowedTools flags:

/Users/.../.local/bin/claude --output-format stream-json --verbose --input-format stream-json 
  --model claude-sonnet-4-5 --resume <session-id> 
  --disallowedTools Bash,Read,Write,Edit,Grep,Glob,WebFetch,WebSearch,Task,NotebookEdit,AskUserQuestion,TodoWrite

Workaround used

# Kill all orphaned node processes in the project directory
lsof +D /path/to/project 2>/dev/null | grep "^node" | awk '{print $2}' | sort -u | xargs kill -9

Observations

  1. Accumulation rate: Roughly 80+ processes per day of active use
  2. Memory impact: Less severe than the 30GB case in the original report, but still significant resource drain
  3. Process type: Mix of subagents spawned via Task tool and potentially MCP server instances
  4. Client difference: This occurred with Claude Desktop, not VS Code terminal - suggesting the issue affects multiple client entry points

This suggests the orphaning happens whenever parent sessions end without proper cleanup, regardless of termination reason (context exhausted, user action, etc.).

madshn · 7 months ago

Fix Verification: v2.1.19 Addresses Both Orphan and Active Session Scenarios

Following up on my earlier comment about the Skill/MCP trigger variant — I've verified that v2.1.19 appears to fix both scenarios.

Test Environment

  • Version: 2.1.19
  • Platform: macOS Darwin 25.2.0 (ARM64)
  • MCP servers: Supabase, Notion

Tests Performed

| Test | Scenario | Result |
|------|----------|--------|
| 1 | Direct MCP call (mcp__supabase__get_logs) | ✅ Passed |
| 2 | Skill wrapper invoking MCP (Notion:notion-find) | ✅ Passed |

Memory Observations

| Phase | Total Claude RSS |
|-------|------------------|
| Before direct MCP | 1.80 GB |
| After direct MCP | 1.61 GB |
| Before Skill wrapper | 1.76 GB |
| After Skill wrapper | 1.66 GB |

No memory spike, no freeze, no uninterruptible state. Normal GC behavior observed.

Relevant Fix in 2.1.19

Fixed dangling Claude Code processes when terminal is closed by catching EIO errors from process.exit() and using SIGKILL as fallback

This fix appears to address the underlying issue that caused both:

  1. Orphaned processes when terminal is closed (original report)
  2. Active session freezes during Skill/MCP calls (my earlier variant report)

Conclusion

The EIO error handling fix likely prevents the process from entering the uninterruptible state that led to the WebKit memory leak. Will continue monitoring in production usage, but initial verification is positive.

madshn · 7 months ago

Update: v2.1.19 Fix Does NOT Address Task/Subagent Trigger

Shortly after my verification comment, encountered the same bug on v2.1.19 with a different trigger pattern.

New Trigger: Task Tool Spawning Subagents

| Detail | Value |
|--------|-------|
| Version | 2.1.19 |
| Trigger | Task tool with n8n-workflow subagent |
| Process state | U+ (uninterruptible) |
| Memory at discovery | 1.1 GB |
| Elapsed time | 6h 20m |
| Zombie children | 2 defunct processes |

Process Tree (Truncated)

53744 U+ 1.1GB claude (STALLED - parent)
├── 49862 Z+ <defunct>  (zombie child - unreapable)
├── 50241 Z+ <defunct>  (zombie child - unreapable)
├── 53816 S+ npm exec @upstash/context7-mcp
├── 53817 S+ npm exec @playwright/mcp@latest
└── 53984 S+ claude --claude-in-chrome-mcp

Analysis

The v2.1.19 fix (EIO error handling) addresses:

  • ✅ Terminal closed → orphaned processes
  • ✅ Direct MCP tool calls
  • ✅ Skill wrapper → MCP calls

But does NOT address:

  • ❌ Task tool spawning subagents that block/timeout

The zombie children indicate the subagent processes terminated but the parent couldn't reap them because it was already in uninterruptible state. This suggests the parent enters U+ state before the children finish, possibly during:

  1. Subagent process spawn
  2. IPC/communication with subagent
  3. Waiting for subagent response

Workaround

kill -9 <PID> successfully terminated the stuck process. Children were automatically cleaned up.

Suggested Investigation

The Task tool's process management may have a different code path than direct MCP calls. Worth checking if the same EIO error handling was applied to subagent spawning/communication.

NathanSkene · 6 months ago

Workaround: Automated cleanup script

I hit the same issue — 407 orphaned processes, 17.8GB RAM, 49GB swap on a 32GB machine, load average 452. Built an automated workaround that's been running reliably for me.

Script: https://gist.github.com/NathanSkene/d1ba60226201dbc998f200f83be8a2c0

What it does

Finds and kills orphaned Claude Code processes (PPID=1) that match .claude/plugins, .claude/shell-snapshots, or @anthropic in their command line. Has a 120-second age guard so it won't kill active subagents.

Setup (macOS)

# Download
mkdir -p ~/.claude/scripts
curl -o ~/.claude/scripts/claude-cleanup.sh \
  https://gist.githubusercontent.com/NathanSkene/d1ba60226201dbc998f200f83be8a2c0/raw/claude-cleanup.sh
chmod +x ~/.claude/scripts/claude-cleanup.sh

# Test (dry run)
~/.claude/scripts/claude-cleanup.sh --dry-run

# Automate: runs every 5 min via launchd + on session exit via Stop hook

Full setup instructions (launchd plist, cron for Linux, Stop hook config) in the gist README.

Becomes a harmless no-op if/when this is fixed upstream.

Roozbehy · 6 months ago

Additional data point: Recurring orphaned subagent accumulation (macOS)

This issue has been affecting me repeatedly over the past several days. The orphaned subagents don't just leak one at a time — they accumulate across sessions, causing severe system-wide degradation.

Timeline of incidents

| Date | Orphaned Claude processes | Load average | RAM (of 32GB) |
|------|--------------------------|--------------|----------------|
| Feb 4 | 214 | 27.41 | 31GB used |
| Feb 5 | 301 | 140.59 | 31GB used, 70MB free |
| Feb 7 | 211 | 12.07 | 31GB used, 96MB free |

Key observations

  • All orphaned processes are subagents spawned by the Task tool, identifiable by --disallowedTools in their command line
  • Closing the parent Claude Code session does not terminate them. Even explicitly closing all Claude Code terminal windows leaves the subagents running indefinitely
  • Processes persist for days — on Feb 7, some orphans had been running since Friday (Feb 5), consuming ~45-250MB each
  • Multiple processes share the same --resume session ID, suggesting failed session resumption attempts that spawn new processes without terminating previous ones
  • Cumulative effect is catastrophic: 211+ processes × ~100-250MB each = massive memory pressure, forcing macOS into continuous memory compression (15GB compressed) and heavy swap (3.6M swapouts), bringing the system to a crawl

System info

  • macOS 15.x (Darwin 25.2.0, ARM64, Apple Silicon)
  • 32GB RAM, 12-core CPU
  • Claude Code used across multiple terminal sessions daily

Workaround

pkill -f "claude.*--disallowedTools"

This kills only the orphaned subagents without affecting active Claude sessions. A cron job running this periodically is a stopgap, but proper process lifecycle management (parent exit → child cleanup) is needed.

theQuert · 6 months ago

For anyone still dealing with this, I put together an automated cleanup solution that handles orphan processes at three layers:

  1. Claude Code Stop hook — runs on session end, kills orphan subagents and MCP servers
  2. proc-janitor daemon — background process that scans every 30s for PPID=1 orphans, with a 60s grace period before SIGTERM→SIGKILL
  3. Shell functionsclaude-ram to check RAM breakdown, claude-cleanup for manual cleanup

The idea is: Stop hook handles normal exits, the daemon catches crashes/force-quits, and shell aliases are there when you just want to check or clean manually.

Repo with one-command installer: https://github.com/theQuert/cc-reaper

In my case this brought usage down from 7.2 GB → 4.5 GB (with 5 sessions running). The main culprits were 13 orphan subagents (~2.6 GB) and 38 duplicate MCP server processes (~1.6 GB) that accumulated over ~12 hours.

jroth1111 · 6 months ago

Evidence Update / Attribution Correction (2026-03-04)

I re-checked the watchdog artifact bundle and need to correct attribution details from my earlier note.

Confirmed for the panic

panic(cpu 0 caller 0xfffffe00534ac00c): watchdog timeout: no checkins from watchdogd in 90 seconds
Compressor Info: 31% of compressed pages limit (OK) and 100% of segments limit (BAD) with 38 swapfiles
0xfffffe252335cf40 codex-aarch64-apple-darwin 31 0
0xfffffe2526c75660 gmail-rag 31 4926186

What I could not confirm for this specific crash

  • No claude / anthropic process entry in the panic file.
  • Jetsam event process lists in the incident window did not contain Claude/Anthropic process names.
  • Unified logs had incidental string hits (for example /Volumes/Claude, com.anthropic.claude LaunchServices lookups), but no claude-code process entries in the critical window.

Practical takeaway

This incident is clearly a watchdog timeout under severe memory pressure. Based on current artifacts, I cannot assert direct Claude Code process causality in this panic path.

Leaving this correction here for accuracy.

theQuert · 5 months ago

Built a community workaround for this: cc-reaper

Three-layer defense against orphaned subagents and MCP servers:

  1. Stop hook — kills orphans on normal session end (TTY filtering to avoid false positives)
  2. proc-janitor daemon — background cleanup for crash scenarios
  3. Shell commandsclaude-ram for monitoring, claude-cleanup for manual kills
git clone https://github.com/theQuert/cc-reaper && cd cc-reaper && bash install.sh

Keeps RAM under control until this is fixed upstream.

NathanSkene · 5 months ago

Follow-up on the claude-cleanup.sh workaround gist after using it in production for a while.

A small bug turned up in the elapsed-time parser on macOS: ps etime values like 08 and 09 were being fed straight into bash arithmetic, which treats them as invalid octal and throws value too great for base. The practical effect was noisy stderr and partial failure in the watchdog run, even though the overall PPID=1 approach was still the right fallback for orphan cleanup.

The gist has now been updated to force base-10 parsing in etime_to_seconds().

Important scope note: this only fixes the watchdog script itself. It does not change the intended scope of the workaround — it still only targets PPID=1 orphaned Claude/MCP trees, not live Claude jobs that are still attached to a VS Code terminal process.

mgorkemuz · 4 months ago

The subagent reparenting described here is in the same family as a broader Bash-tool child orphan problem. Different pid source, same lifecycle: parent terminal dies, child reparents to launchd (PPID=1), accumulates memory forever. For the Bash-tool slice (npm run dev, next dev, vitest --watch, and similar), I've published a plugin: claude-code-shepherd.

Relevant bits for the reproduction in this issue:

  • Tracks every Bash-tool child on spawn including the full subtree under the session that started it. State lives on disk at ~/.claude/.shepherd/<id>.json, so it survives a Claude crash.
  • On the next Claude launch, the SessionStart hook scans every tracked session whose Claude pid is no longer alive, SIGTERM then SIGKILLs the leftover trees, and posts a short summary. Default on. This is the closest thing to the "children should die when the terminal dies" ask, deferred to the next launch rather than instant.
  • Surgical kill by session or by pid from inside any live Claude session, so accidental VS Code terminal close does not force kill -9 chases.

The specific subagent in the reproduction is spawned via the Task tool, outside shepherd's tracking filter. The subagent family is cc-reaper's domain. Running both side by side covers the combined surface.

github-actions[bot] · 3 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

github-actions[bot] · 1 month ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.