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

Resolved 💬 5 comments Opened May 23, 2026 by nitsanavni Closed May 28, 2026

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 ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗