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

  1. Workers outlive a daemon upgrade restart. The daemon log shows binary ... changed (mtime changed) — self-restarting for upgrade, then shutting 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's stale-spare / idle-prompt retire only seems to cover workers it spawned itself.
  2. The low-memory gate does not see the cgroup limit. Refill is skipped when lowMem is set (from memoryPressureLevel()). In a container at its cgroup limit (memory.pressure full avg300 ≈ 97%, swap 1023/1024 MiB, load ≈ 65 on 4 cores), the daemon still logged bg spare spawned after each claim. It looks like the pressure reading comes from the host, not the container's cgroup (/sys/fs/cgroup/memory.pressure, memory.max).
  3. Claimed workers keep the --bg-spare argv. After bg claimed-spare <id>, the process still reads claude bg-spare --bg-spare .../spare/<x>.claim.sock. From ps an 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.
  4. Volume. Over about two weeks the log shows 657 claimed-spare events (340 fleet, 161 spare, 119 slash, 37 shell). 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).

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗