Background daemon: bg-spare workers survive upgrade restarts, low-memory gate ignores cgroup limits, claimed workers indistinguishable from spares
Status Open
Reported on v2.1.289
Maintainer reply None cached
Workaround ✓ Mentioned in description ↑
Activity 1 comment · opened Oct 6, 2026
Summary
On a Linux host where several Claude Code sessions share one background daemon, bg-spare processes pile up and stay resident long after they are useful. In one container I counted 7 to 8 bg-pty-host/bg-spare pairs (about 1.6 GB RSS) at once, some up to 41 hours old. With swap full, the daemon's low-memory gate never stopped it from refilling.
Version: 2.1.289 (also seen on 2.1.281). Linux, inside an LXC container with a 9 GiB cgroup memory limit on a 16 GiB host.
Observations
- Workers outlive a daemon upgrade restart. The daemon log shows
binary ... changed (mtime changed) — self-restarting for upgrade, thenshutting down (cause=upgrade, ..., live_workers=5). Afterwards, a claimed worker spawned by the old daemon was still running with parent PID 1, 41 hours later. Nothing retired it: the new daemon'sstale-spare/idle-promptretire only seems to cover workers it spawned itself. - The low-memory gate does not see the cgroup limit. Refill is skipped when
lowMemis set (frommemoryPressureLevel()). In a container at its cgroup limit (memory.pressurefull avg300 ≈ 97%, swap 1023/1024 MiB, load ≈ 65 on 4 cores), the daemon still loggedbg spare spawnedafter each claim. It looks like the pressure reading comes from the host, not the container's cgroup (/sys/fs/cgroup/memory.pressure,memory.max). - Claimed workers keep the
--bg-spareargv. Afterbg claimed-spare <id>, the process still readsclaude bg-spare --bg-spare .../spare/<x>.claim.sock. Frompsan idle spare and a live claimed session look identical, so a user cleaning up idle spares by age can end real sessions by mistake. The only way to tell them apart is matching the daemon log by time. - Volume. Over about two weeks the log shows 657
claimed-spareevents (340fleet, 161spare, 119slash, 37shell). Each claim refills the slot. That's fine on its own, but combined with 1 to 3 above, the leftovers add up on a small host.
Expected
- After an upgrade restart, the new daemon adopts the old workers or retires them under the same idle rules.
- Inside a cgroup, the low-memory check reads the cgroup's own pressure and limit.
- A claimed worker's process title shows it is no longer a spare.
- Optionally, a user setting to turn spares off or cap them (for example an env var), for small or shared hosts.
Workaround in use
A local script ends spare pairs older than an hour, but it has to check the daemon log first so it skips claimed ones (see 3).
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗