bg daemon memory governor kills mid-flight workers on 16 GB Macs (gates spawns on os.freemem() under-read)
Summary
On a 16 GB Mac, the background-session daemon's memory governor kills mid-flight bg workers because it gates worker spawns on Node's os.freemem(), which chronically under-reads on macOS (it counts only genuinely-free pages and ignores the compressor's reclaimable memory). The result is a self-inflicted kill-storm: background sessions die when reactivated from Agent View, and some die before any user action while resuming mid-processing.
This is distinct from #63023 (workers dying on daemon self-restart/adopt after an auto-update). No update is involved here — it reproduces with a stable binary and idle updater.
Environment
- macOS (Apple Silicon), 16 GB RAM
- Claude Code 2.1.201 (daemon running continuously ~1d14h, no auto-update in the window)
What happens
Before spawning/claiming a bg worker, the daemon logs:
[bg] bg: low memory (NNMB free) — retiring settled workers before spawning <id>
and retires workers. In the churn it also kills workers that were just claimed/spawned and are still running:
[bg] bg claimed-spare 88006bfc (fleet) # 13:18:02
[bg] bg settled 88006bfc (killed) # 13:18:20 -> 18s later
[bg] bg spawned 5332df61 (fleet) # 13:30:18
[bg] bg settled 5332df61 (killed) # 13:30:32 -> 14s later
The NNMB free figure is os.freemem(). On this machine it reads ~79 MB while the OS is genuinely at 39% free (~6.4 GB), with zero jetsam events and 1.68 M pages sitting in the compressor. So the governor's trigger is essentially always tripped, and it fires on nearly every spawn (141 times over 2 days). With a warm fleet of ~12 bg sessions plus a spare pool, every fleet spawn/refill re-runs the governor.
At least once the governor fired while logging low memory (1565MB free) — retiring even with 1.5 GB reported free — suggesting the threshold is high and/or scaled by pending spawns.
Impact
- Background/Agent-View sessions are repeatedly killed mid-run on 16 GB machines under normal multi-session use.
- Kills hit brand-new sessions as readily as old ones — the death happens during the worker spawn, independent of session age or binary version, which makes it look like "random crashes even on fresh sessions."
- Transcripts persist on disk and sessions resume with full context, so no data is lost — but the churn is disruptive and makes Agent View feel unstable.
Suspected root cause
The memory governor gates worker spawns on os.freemem(), which is not a valid signal for available memory on macOS (excludes compressor/cache/reclaimable). It should use compressor-aware / memory_pressure-based accounting (or macOS memorystatus levels) before retiring/killing workers.
Repro
- On a 16 GB Mac, keep ~10+ background sessions warm (Agent View /
claude --bg). - Reactivate sessions from Agent View over a period of time.
- Observe
~/.claude/daemon.log— frequentbg: low memory (…) — retiring settled workers before spawninglines andbg settled <id> (killed)within seconds ofclaimed-spare/spawned. - Confirm it is not real pressure:
node -e 'console.log(require("os").freemem()/1048576|0,"MB")'(~80 MB) vsmemory_pressure | grep "free perc"(~39%) andlog show --predicate 'eventMessage CONTAINS "jetsam"' --last 1d(empty).
Suggested fixes
- Replace the
os.freemem()gate with a compressor/pressure-aware check (e.g.memory_pressure,memorystatus/vm.memory_pressure), so the governor only retires when the OS is actually under pressure. - Never retire/kill a worker that was claimed/spawned within the last N seconds (protect in-flight workers from the retirement pass).
- Consider scaling the warm-fleet / spare-pool size to available RAM to reduce spawn churn on low-RAM machines.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗