[BUG] Mac app: orphaned claude-code CLI from ccd_session chaptered sub-task spins at 100% CPU indefinitely after parent exits non-gracefully
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.0is 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-desktopCLAUDE_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 (
lsofshows-> (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=4pending events). The CPU burn is pure JS on the main thread, no I/O. - The process does not respond to
SIGTERMwhile spinning in this post-disconnect loop (it never returns to the event loop to dispatch the signal handler).SIGKILLis required to terminate it. - The orphan also still carries a live
CLAUDE_CODE_OAUTH_TOKENin its environment (visible viaps -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)
- Run Claude.app.
- 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. - Force-quit Claude.app, or let an auto-update trigger mid-task, or crash the renderer.
- Check
ps -ax | grep claude. The sub-task'sclaudeCLI will be orphaned with PPID=1 and pegging a core.
Suggested fixes
- 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). Onpeer == (none)or heartbeat-lost, self-terminate. - Break out of the post-disconnect tight loop so
SIGTERMis actually observed — e.g. periodicsetImmediate/ cooperative yield in the stream-json consumer, or register the signal handler onprocessbefore entering any synchronous drain loop. - On Claude.app startup, sweep PPID=1 claude-code CLIs whose
--plugin-dirpoints into the samelocal-agent-mode-sessions/<tenant>/<install>/as the currently-running app, or whose cmdline references an olderclaude-code/<version>/bundle, and reap them. - Before an auto-update replaces the current app instance, broadcast
SIGTERM(andSIGKILLafter a grace period) to all live CLI children. Thedisclaimerwrapper ancestor is already in the tree — forwarding the signal should suffice. - Redact
CLAUDE_CODE_OAUTH_TOKENfrom the child'sprocess.envafter 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}')This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗