Orphaned bg-pty-host (spare) busy-loops at 100% CPU for days after its daemon dies
Summary
A claude --bg-pty-host process for a spare slot can become orphaned (reparented to launchd, PPID 1) when its parent daemon goes away, and instead of exiting it busy-loops at ~100% CPU (one full core) indefinitely. I found one that had been pegging a core for ~2 days 21 hours.
This is distinct from the existing stale-daemon / orphan-dir reports (#60634, #61740, #59551): those manifest as crashes (ENOENT), leftover session dirs, or short-lived orphan children. Here the orphan is long-lived and CPU-bound — it never exits and never gets reaped.
Environment
- Claude Code: 2.1.148
- OS: macOS 15.7.2 (build 24G325)
- Arch: x86_64
- Usage pattern: many concurrent
claudesessions (tmux), so daemon/spare churn is frequent; the process's ~3-day age spans at least one version upgrade.
What I observed
Top CPU consumer, captured via ps -Ao pid,ppid,%cpu,stat,etime,command -r:
PID PPID %CPU STAT ELAPSED COMMAND
64924 1 100.0 RNs 02-21:46:27 .../@anthropic-ai/claude-code/bin/claude.exe --bg-pty-host /tmp/cc-daemon-501/cd00dee7/spare/...
Key signals:
PPID 1— the real parent (the daemon) is gone; the process was reparented to launchd.--bg-pty-hostfor aspareslot — path is/tmp/cc-daemon-501/<daemon-id>/spare/...; the referenced daemon dir (cd00dee7) no longer exists.STAT RNs, ~100% CPU, elapsed 2d 21h — it had been spinning a full core for nearly three days. It was older than the currently-running daemon, confirming it belongs to a dead daemon instance.- It serves no live session — no descendant of the current daemon, not attached to any terminal.
Resolution / confirmation
A plain SIGTERM cleared it (no -9 needed). CPU immediately dropped back to normal, no orphan respawned, and /tmp/cc-daemon-501/ is now empty. So the orphan was pure waste — it had no remaining purpose, it just never exited.
Safe one-liner to detect/clear the leaked spinners (only targets bg-pty-host orphaned at PPID 1 and over 50% CPU — never a live one):
ps -Ao pid,ppid,%cpu,command | awk '$2==1 && /bg-pty-host/ && $3>50 {print $1}' | xargs -r kill
Expected behavior
When a bg-spare daemon exits or restarts, its spare bg-pty-host children should detect the dead parent/socket and exit, not busy-loop. An orphaned pty-host with no live consumer should never sit at 100% CPU.
Likely cause (inference, not verified)
The spare bg-pty-host appears to poll its control socket / parent fd in a tight loop. When the daemon dies and the socket/fd goes away, the poll returns readable-but-EOF (or EBADF/ECONNRESET) on every iteration and the loop never breaks → 100% CPU. Adding "parent gone ⇒ exit" (e.g. PR_SET_PDEATHSIG equivalent / detect PPID==1 / treat socket EOF as terminal) would fix it.
Repro
I don't have deterministic steps — observed in the wild. Conditions that seem to set it up: run several concurrent sessions so spares get created, then have the daemon die/restart (e.g. across an upgrade) while a spare pty-host exists. The orphaned host then spins. Happy to capture more (sample/spindump of the spinning PID) if it recurs.
6 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Thanks bot — these are related but not duplicates. They share the surface symptom (
claudeatPPID 1, ~100% CPU busy-loop, lives for hours/days), but this report differs from all three on the axes that actually define the bug:| | This issue (#61800) | #51860 | #47872 (closed) | #56914 (closed) |
|---|---|---|---|---|
| Orphaned process |
bg-pty-host, spare slot | session CLI sub-task (chaptered) | session CLI (--resume) | session CLI || Parent that died | bg daemon | Claude.app (auto-update) | terminal / Claude.app | terminal / app window |
| Serves a live session? | No — sessionless spare | Yes — tool call in flight | Yes — resumed session | Yes — clean-ended session |
| Cleared by |
SIGTERM✅ |SIGKILLonly |SIGKILLonly |SIGHUPnot honored || Trigger | daemon dies/restarts while a spare exists | parent dies mid-subtask | terminal closed | window closed w/o
/exit|The decisive tell is the signal behavior: this orphan's loop still returns to the event loop, so a plain
SIGTERMclears it — consistent with a poll-on-dead-socket spin inside the spare PTY-host. The other three are stuck in a synchronous post-disconnect loop (requireSIGKILL) inside a session-bearing CLI killed by terminal/app death. Different component, different parent, different loop. Also: #47872 and #56914 are already closed, so this can't be a duplicate of them.The closest cousin is #51860 — it's plausible the underlying defect is the same class (Node not surfacing unix-socket peer-death as EOF → tight loop). But it manifests in a different component (chaptered sub-task CLI under Claude.app vs. a sessionless daemon spare
bg-pty-host) and has the opposite signal behavior (SIGKILL-only vs.SIGTERM-clears), so I'd keep them as separate reports even if they get fixed together.Keeping this open — please don't auto-close as a dup.
Recurrence + two new observations
Hit this again today (2026-05-24), one day after filing — same machine, same
2.1.148, macOS 15.7.2 (24G325), x86_64. New orphan PID this time, same failure mode:PPID 1, ~99% CPU, elapsed 2d 14h (started May 21), and older than the currently-running daemon (80973, up ~1d22h) — so again it belongs to a dead daemon instance. Same daemon dir (cd00dee7). PlainSIGTERMcleared it, no respawn. Confirms it's not a one-off.Two things beyond the original report:
1. The orphan can have a surviving
--bg-sparechild that the detector misses. This orphan had a child:The child's
PPIDis the orphan (22399), not 1, so the$2==1detector one-liner won't catch it on its own — you have to kill the pty-host parent (which takes the child with it) or match the wholecd00dee7tree. Worth widening the cleanup snippet to sweep the children too, e.g.:**2. The 100% spin may not be unique to the orphaned (dead-daemon) case. At the same time, two
--bg-spareprocesses that were** attached to the live daemon (80973) were also burning CPU:Both are long-lived spares (10.5h / 13.8h) under the current daemon, spinning at 30–60% of a core, while freshly-spawned spares from the same daemon sat near-idle (<1%). So the busy-loop seems to correlate with spare age / time-unclaimed, and losing the parent just removes the only thing that would ever have reaped them — i.e. the orphan case is the most visible symptom of a more general "idle spare spins" problem, not a separate bug. (Inference, not confirmed from source.)
This issue was fixed as of version 2.1.154.
Still occurring on v2.1.161 (2026-06-03).
Environment:
A
--bg-spare/--bg-pty-hostprocess survived overnight (~281 min CPU time), pinning one core at 100% CPU. The process was reparented to launchd (PPID 1) and was not cleaned up byclaude-cleanup. Requiredkill -9to terminate.See also #63196 (my original report from v2.1.153) which was closed as fixed but has regressed again in v2.1.161.
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.