claude-code - 💡(How to fix) Fix [BUG] Mac app: orphaned claude-code CLI from ccd_session chaptered sub-task spins at 100% CPU indefinitely after parent exits non-gracefully [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#51860Fetched 2026-04-23 07:42:58
View on GitHub
Comments
2
Participants
2
Timeline
7
Reactions
0
Timeline (top)
labeled ×5commented ×2

Fix Action

Fix / Workaround

Why it survives

  • Its stdin/stdout/stderr/IPC channels are unix sockets. The peer side is gone (lsof shows -> (none) on fds 0/1/2/4/5), but Node on the CLI side does not observe peer death on unix sockets the way it does on pipes — no EOF is surfaced to the stream-json reader, so the event loop never gets a stream-end signal.
  • A tool call was in flight at the moment the parent died; the JS runtime keeps the kqueue alive waiting for a reply that will never come. 3 KQUEUEs are still registered (one with count=4 pending events). The CPU burn is pure JS on the main thread, no I/O.
  • The process does not respond to SIGTERM while spinning in this post-disconnect loop (it never returns to the event loop to dispatch the signal handler). SIGKILL is required to terminate it.
  • The orphan also still carries a live CLAUDE_CODE_OAUTH_TOKEN in its environment (visible via ps -E) — a secondary concern for users who don't notice the leak.

Workaround for affected users

SIGTERM will not work; use SIGKILL:

Code Example

--allowedTools mcp__computer-use,mcp__ccd_session__spawn_task,mcp__ccd_session__mark_chapter
--disallowedTools AskUserQuestion
--replay-user-messages
--include-partial-messages

---

ps -axo pid,ppid,etime,%cpu,command |
  awk '$2==1 && /claude-code\/[0-9.]+\/claude\.app\/Contents\/MacOS\/claude/'

---

kill -9 $(ps -axo pid=,ppid=,command= |
          awk '$2==1 && /claude-code\/[0-9.]+\/claude\.app\/Contents\/MacOS\/claude/ {print $1}')
RAW_BUFFERClick to expand / collapse

Environment

  • Claude.app (Desktop) 1.3883.0 (incident originated on 1.3109.0 and survived the upgrade — a chrome_crashpad_handler --annotation=_version=1.3109.0 is still running alongside the current 1.3883 main)
  • Bundled claude-code CLI 2.1.111 (CLAUDE_AGENT_SDK_VERSION=0.2.111)
  • macOS 15.5 (Sequoia), Intel MacBook Pro 16,1 (i7-9750H, 16 GB RAM)
  • CLAUDE_CODE_ENTRYPOINT=claude-desktop
  • CLAUDE_INTERNAL_FC_OVERRIDES={"tengu_ccr_bridge":true}

What happened

A chaptered / spawned sub-task — identifiable on the command line by

--allowedTools mcp__computer-use,mcp__ccd_session__spawn_task,mcp__ccd_session__mark_chapter
--disallowedTools AskUserQuestion
--replay-user-messages
--include-partial-messages

was running over the Tengu↔CCR IPC bridge. The Claude.app parent exited non-gracefully (appears to coincide with an auto-update from 1.3109 → 1.3883). The child claude CLI was reparented to launchd (PPID=1) and has been running continuously for 2 d 4 h, pegging a CPU core at ~92–100 %, holding ~316 MB RSS.

The session's JSONL transcript recorded 18 entries over ~35 seconds and then stopped writing; the process has kept running for two more days. Last recorded action was an mcp__ccd_session__mark_chapter followed by a ToolSearch tool call that never received a response.

Why it survives

  • Its stdin/stdout/stderr/IPC channels are unix sockets. The peer side is gone (lsof shows -> (none) on fds 0/1/2/4/5), but Node on the CLI side does not observe peer death on unix sockets the way it does on pipes — no EOF is surfaced to the stream-json reader, so the event loop never gets a stream-end signal.
  • A tool call was in flight at the moment the parent died; the JS runtime keeps the kqueue alive waiting for a reply that will never come. 3 KQUEUEs are still registered (one with count=4 pending events). The CPU burn is pure JS on the main thread, no I/O.
  • The process does not respond to SIGTERM while spinning in this post-disconnect loop (it never returns to the event loop to dispatch the signal handler). SIGKILL is required to terminate it.
  • The orphan also still carries a live CLAUDE_CODE_OAUTH_TOKEN in its environment (visible via ps -E) — a secondary concern for users who don't notice the leak.

Detection one-liner

ps -axo pid,ppid,etime,%cpu,command |
  awk '$2==1 && /claude-code\/[0-9.]+\/claude\.app\/Contents\/MacOS\/claude/'

Any match with etime more than a few minutes is a leak.

Repro (probabilistic — requires the parent to die mid-subtask)

  1. Run Claude.app.
  2. In an agent session, trigger a chaptered sub-task (via mcp__ccd_session__spawn_task) that issues at least one tool call and is likely to take >30 s.
  3. Force-quit Claude.app, or let an auto-update trigger mid-task, or crash the renderer.
  4. Check ps -ax | grep claude. The sub-task's claude CLI will be orphaned with PPID=1 and pegging a core.

Suggested fixes

  1. In the CLI's stream-json input path, install peer-liveness detection (heartbeat or explicit SIGHUP / disconnect handshake from the Mac-app parent before it exits). On peer == (none) or heartbeat-lost, self-terminate.
  2. Break out of the post-disconnect tight loop so SIGTERM is actually observed — e.g. periodic setImmediate / cooperative yield in the stream-json consumer, or register the signal handler on process before entering any synchronous drain loop.
  3. On Claude.app startup, sweep PPID=1 claude-code CLIs whose --plugin-dir points into the same local-agent-mode-sessions/<tenant>/<install>/ as the currently-running app, or whose cmdline references an older claude-code/<version>/ bundle, and reap them.
  4. Before an auto-update replaces the current app instance, broadcast SIGTERM (and SIGKILL after a grace period) to all live CLI children. The disclaimer wrapper ancestor is already in the tree — forwarding the signal should suffice.
  5. Redact CLAUDE_CODE_OAUTH_TOKEN from the child's process.env after startup (defence in depth).

Related issues (pattern, not duplicates)

  • #51264 (channel plugin bun orphans)
  • #51026 (app-server-broker leak on abnormal exit)
  • #1935 (MCP servers orphan on exit)
  • #44507 (CLI survives terminal close, no stdin end handler) — closed as dup; this issue is the Mac-app Tengu-bridge variant where the parent is the Claude.app renderer, not a terminal.

Workaround for affected users

SIGTERM will not work; use SIGKILL:

kill -9 $(ps -axo pid=,ppid=,command= |
          awk '$2==1 && /claude-code\/[0-9.]+\/claude\.app\/Contents\/MacOS\/claude/ {print $1}')

extent analysis

TL;DR

The most likely fix is to implement peer-liveness detection in the CLI's stream-json input path to self-terminate when the parent process exits.

Guidance

  • Implement a heartbeat or explicit disconnect handshake from the Mac-app parent before it exits to detect peer liveness.
  • Break out of the post-disconnect tight loop to allow SIGTERM to be observed, using techniques like periodic setImmediate or cooperative yield.
  • Sweep and reap orphaned CLI processes on Claude.app startup to prevent resource leaks.
  • Broadcast SIGTERM and SIGKILL to live CLI children before an auto-update replaces the current app instance.

Example

# Example of how to implement peer-liveness detection using a heartbeat
while true; do
  # Read from the stream-json input
  read -r line
  # Check if the parent process is still alive
  if [ -z "$line" ]; then
    # If not, self-terminate
    exit 1
  fi
done

Notes

The provided guidance is based on the information given in the issue and may not be exhaustive. Additional fixes, such as redacting CLAUDE_CODE_OAUTH_TOKEN from the child's process.env, may be necessary to fully resolve the issue.

Recommendation

Apply the suggested fixes, starting with implementing peer-liveness detection, to prevent the CLI process from running continuously and consuming system resources.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING