[BUG] Background daemon never reaps stale workers or unclaimed spares — they are re-adopted on every restart (64 leaked processes / ~7.1 GB after six weeks)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The background-session daemon never reaps stale workers or unclaimed spares. Worse, on every self-restart it re-adopts them, so the dead weight is inherited indefinitely rather than cleared. After six weeks of normal use I had 64 leaked claude processes holding ~7.1 GB RSS, none of which had done anything for 25+ days.
The daemon's own log shows it knows they are stale and declines to clean them up. From today, on 2.1.235, immediately after a routine upgrade restart:
[2026-08-19T05:24:49.885Z] [supervisor] binary at ~/.local/bin/claude changed (2.1.233 → 2.1.235) — self-restarting for upgrade
[2026-08-19T05:24:49.908Z] [supervisor] shutting down (cause=upgrade, uptime=170930s, leases=0, live_workers=32)
[2026-08-19T05:24:50.154Z] [supervisor] ─── daemon start ─── version=2.1.235 pid=70841 origin=transient
[2026-08-19T05:24:50.188Z] [bg] bg adopt: adopted=32 respawned=0 dead=0
[2026-08-19T05:24:50.191Z] [bg] bg orphan-spare reap: 1
[2026-08-19T05:25:05.208Z] [bg] bg: post-takeover prewarm burst — respawned 0/32 stale workers, 32 refused in 0s
adopted=32 ... dead=0 — the new daemon takes ownership of 32 workers and classifies none of them as dead. Fifteen seconds later it tries to respawn them, all 32 refuse, and it calls them "stale" in its own log message. Nothing then removes them. orphan-spare reap: 1 is the only reclamation that happens, and it reclaims a single process.
This is not new to 2.1.235 — the identical shape appears at every upgrade restart in my log going back weeks, e.g. 2.1.217→2.1.218 (adopted=33 respawned=0 dead=0, orphan-spare reap: 1, respawned 1/33 stale workers, 32 refused).
Breakdown of what leaked
64 processes, age ≥ 25 days, ~7.1 GB RSS:
- 22 processes = 11 wedged background session workers (a
bg-pty-hostplus its--session-id … --fork-session --resume …child), ~2.8 GB. Each reports toclaude agents --jsonasbackground / blocked / idle, i.e. still "active". Ten of the eleven never wrote a transcript at all; the eleventh wrote a 0-byte file. They are running binaries from 2.1.202–2.1.218 that have long since been superseded. - The remainder are pre-warmed spares and their pty hosts that were spawned, never claimed, and never released.
A worked example, traceable end-to-end in the log. Session 6ffcdf0e was spawned, and 17 minutes later an upgrade restart occurred:
[2026-07-22T05:40:40.860Z] [bg] bg spawned 6ffcdf0e (slash)
[2026-07-22T05:40:40.865Z] [bg] bg spare spawned host pid=92993
[2026-07-22T05:58:39.222Z] [supervisor] binary changed (2.1.216 → 2.1.217) — self-restarting for upgrade
[2026-07-22T05:58:39.472Z] [supervisor] ─── daemon start ─── version=2.1.217 pid=67516 origin=transient
[2026-07-22T05:58:39.472Z] [supervisor] ─── daemon start ─── version=2.1.217 pid=67499 origin=transient
[2026-07-22T05:58:39.486Z] [supervisor] another daemon won the lock race (pid=67499) — exiting
[2026-07-22T05:58:39.507Z] [supervisor] ─── daemon start ─── version=2.1.217 pid=67520 origin=transient
[2026-07-22T05:58:39.508Z] [supervisor] ─── daemon start ─── version=2.1.217 pid=67521 origin=transient
[2026-07-22T05:58:39.519Z] [supervisor] another daemon is already running (pid=67499, …)
[2026-07-22T05:58:39.531Z] [bg] bg adopt: adopted=34 respawned=0 dead=0
There is no bg settled 6ffcdf0e line anywhere in the log, before or since. It was adopted by the lock-race winner and never settled. Its process pair is still resident 28 days later.
Two things stand out in that excerpt beyond the leak itself:
- Four daemons are launched simultaneously and race for one lock. Three lose and exit. This is presumably benign by design, but it is the moment the worker's ownership is lost.
- A worker adopted during that handover can end up with no lifecycle owner — never settled, never respawned, never reaped, but still counted as an active background session by
claude agents.
Steps to Reproduce
- Use background agents regularly (
claude --bg, the Agent tool,/bg) across several repos, over a period of weeks, on a machine that stays powered on. - Let Claude Code auto-update a few times so the daemon performs its upgrade self-restart.
- Inspect the resident process set:
ps -axo pid,ppid,etime,rss,command | grep claude - Inspect
~/.claude/daemon.logforbg adopt:,orphan-spare reap:andpost-takeover prewarm burstlines.
Expected Behavior
- Workers that refuse respawn and are classified as stale during the post-takeover burst are terminated, not carried forward.
- A worker adopted during a daemon handover that never re-attaches to a client is eventually settled and reaped, and stops being reported as an active background session.
- Unclaimed pre-warm spares have a bounded lifetime.
Actual Behavior
Stale workers and unclaimed spares accumulate without limit and are re-adopted on every daemon restart. On this machine that reached 64 processes and ~7.1 GB, with 11 of them reported indefinitely as live background / blocked / idle sessions despite having written no transcript and being weeks dead. Manually killing them is currently the only remedy.
Environment
- macOS, Apple Silicon (arm64)
- Claude Code 2.1.235 (native CLI install under
~/.local/share/claude/versions/); leaked processes span 2.1.202 – 2.1.218 - Daemon is
origin: transientper~/.claude/daemon.lock - Usage pattern: many concurrent background agents across multiple repositories, machine rarely rebooted
Related
- #78454 — stale worker entries in
roster.jsonsurviving process death (the ledger-side counterpart; this report is the process side) - #87813 — background shell processes not cleaned up on session exit (different subsystem: Bash tasks, not daemon workers/spares)
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗