Orphaned bg-pty-host (spare) busy-loops at 100% CPU for days after its daemon dies

Status Fixed / completed
Reported on v2.1.148
Maintainer reply ✓ Yes — claude[bot]
Activity 6 comments · opened May 23, 2026 · closed May 28, 2026
💡 Likely answer: A maintainer (claude[bot], contributor) responded on this thread — see the highlighted reply below.

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 claude sessions (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-host for a spare slot — 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.

View original on GitHub ↗

6 Comments

github-actions[bot] · 3 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/51860
  2. https://github.com/anthropics/claude-code/issues/47872
  3. https://github.com/anthropics/claude-code/issues/56914

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

nitsanavni · 3 months ago

Thanks bot — these are related but not duplicates. They share the surface symptom (claude at PPID 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 ✅ | SIGKILL only | SIGKILL only | SIGHUP not 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 SIGTERM clears 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 (require SIGKILL) 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.

nitsanavni · 3 months ago

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:

  PID  PPID  %CPU     ELAPSED COMMAND
22399     1  99.6 02-14:50:25 .../claude.exe --bg-pty-host /tmp/cc-daemon-501/cd00dee7/spare/5cd64faf.pty.sock 200 50 -- ... --bg-spare .../5cd64faf.claim.sock

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). Plain SIGTERM cleared it, no respawn. Confirms it's not a one-off.

Two things beyond the original report:

1. The orphan can have a surviving --bg-spare child that the detector misses. This orphan had a child:

  PID  PPID  %CPU COMMAND
22406 22399   0.1 .../claude.exe --bg-spare /tmp/cc-daemon-501/cd00dee7/spare/5cd64faf.claim.sock

The child's PPID is the orphan (22399), not 1, so the $2==1 detector 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 whole cd00dee7 tree. Worth widening the cleanup snippet to sweep the children too, e.g.:

# kill orphaned bg-pty-host (PPID 1, >50% CPU) AND its bg-spare children
ps -Ao pid,ppid,%cpu,command | awk '$2==1 && /bg-pty-host/ && $3>50 {print $1}' | while read p; do
  pkill -P "$p"; kill "$p"
done

**2. The 100% spin may not be unique to the orphaned (dead-daemon) case. At the same time, two --bg-spare processes that were** attached to the live daemon (80973) were also burning CPU:

  PID  PPID  %CPU     ELAPSED COMMAND
69919 69914  59.1    10:33:18 ...--bg-spare .../e53c7878.claim.sock   (69914 <- 80973, live daemon)
41722 41665  33.4    13:48:42 ...--bg-spare .../ec0d9fa8.claim.sock   (41665 <- 80973, live daemon)

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.)

claude[bot] contributor · 3 months ago

This issue was fixed as of version 2.1.154.

linfeng16 · 2 months ago

Still occurring on v2.1.161 (2026-06-03).

Environment:

  • Claude Code version: 2.1.161
  • macOS: 15.7.7 (Build 24G720)
  • Architecture: x86_64 (Intel)

A --bg-spare / --bg-pty-host process 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 by claude-cleanup. Required kill -9 to terminate.

See also #63196 (my original report from v2.1.153) which was closed as fixed but has regressed again in v2.1.161.

github-actions[bot] · 24 days 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.