Background-session daemon fd storm exhausts the system file table (ENFILE) → launchd SIGBUS → kernel panic
Environment
- Claude Code v2.1.216 (native install,
~/.local/bin/claude), Claude Max plan - macOS 26.5.2, Apple Silicon Mac mini (14 cores, 64 GB RAM), APFS, FileVault on
- Headless automation box: multiple background (host-managed / Agent-view) sessions accumulate; several LaunchAgents also start headless
claude -psessions at login kern.maxfiles= 491,520 (default)
Summary
When the daemon starts with many host-managed background sessions in ~/.claude/daemon/roster.json (we had 10), restoring them all simultaneously can drive the bg-spare pool into a claim/crash/respawn loop that opens file descriptors at ~30,000/sec until the system-wide file table is exhausted. Once kern.num_files hits kern.maxfiles, every open() on the machine fails (ENFILE):
- new processes can't spawn (sshd accepts TCP but can't fork a session; terminals die; GUI wedges with a live cursor on a black screen)
- arbitrary daemons take SIGBUS from failed pageins
- eventually launchd itself takes SIGBUS and the kernel panics (
initproc exited -- exit reason namespace 2 subcode 0xa)
We recorded two kernel panics (2026-07-15 and 2026-07-21 10:02, identical signature) and ~5 full userspace wedges requiring hard power-cycle in a single day, all with this mechanism, and captured the detonation live twice with per-second sampling.
Captured evidence
1. The detonation, 5-second sampling (2026-07-21):
14:32:03 kern.num_files=6,004 claude procs=38
14:32:09 kern.num_files=187,079 claude procs=34 ← +181k open files in 6 seconds
14:32:14 machine unreachable (sshd cannot fork)
2. Top fd holders 60s before detonation (absent from the top-20 one minute earlier; #1 and #2 system-wide):
298 fds claude bg-spare --bg-spare /tmp/cc-daemon-501/c9fbe315/spare/d355817b.claim.sock
296 fds claude bg-spare --bg-spare /tmp/cc-daemon-501/c9fbe315/spare/ca08dfad.claim.sock
3. Second detonation, 1-minute sampling (same day, after reboot, when the daemon restored the same roster):
15:05:01 5,027 load 1.71
15:06:01 5,351 load 5.98 ← daemon restoring 10 background sessions
15:08:01 6,275 load 13.43
15:09:01 89,583 load 21.57 ← storm underway
15:10 (machine dead before next sample)
4. System-daemon collateral at ENFILE (from /Library/Logs/DiagnosticReports):
searchpartyd:EXC_BAD_ACCESS SIGBUS — FS pagein error: 23 Too many open files in systemlocationd:SIGBUS KERN_MEMORY_ERROR(pagein failure)ReportCrashitself:SIGBUS KERN_MEMORY_ERROR(the crash reporter crashing)
5. Kernel panic signature (2026-07-15 and 2026-07-21, identical): launchd (PID 1) SIGBUS → initproc exited -- exit reason namespace 2 subcode 0xa, followed by a watchdog reset on the next boot.
6. In-product symptoms while the table is exhausted (all seen in the TUI):
Couldn't start the session — ENFILE: file table overflow, mkdir '/Users/<user>/.claude/jobs/<id>/tmp'
host-managed tombstone unreadable: ENFILE: file table overflow, access '/Users/<user>/.claude/daemon/host-managed/e6b66f4c'
Not logged in · Please run /login (spurious — auth state file can't be opened)
✘ Auto-update failed · Run claude doctor
Agent view flickers between "no sessions" and "all sessions" — consistent with the supervisor crash-looping (each respawn re-registers the roster, then dies).
7. Interrupted-write debris: ~/.claude/daemon/roster.json.tmp.<hash> files timestamped at the exact crash minutes (14:18, 14:32, 14:33) — the daemon died mid-atomic-write repeatedly.
A/B isolation of the trigger
- A (detonates):
roster.jsoncontains 10 workers → launchclaude→ daemon restores all 10 simultaneously → 13 → 50 claude processes within ~90s → fd storm → machine dead in minutes. Reproduced twice. - B (healthy): identical machine state, same LaunchAgents, but
roster.jsonmoved aside so the daemon starts with zero workers → the same eventual workload (50–60 claude processes, incl. interactive resumes, background tasks, test suites) runs indefinitely at ~5–7k open files.
Simultaneity of restore is the variable, not total load. Resuming the same sessions one at a time (claude --resume, staggered) costs ~40 fds each and is completely benign.
Steady-state observation (secondary)
Even when healthy, the spare pool over-provisions: we observed 29 claude bg-spare processes totaling 12.2 GB RSS for ~15 live sessions. Not fatal, but the same pool that detonates under stampede also over-allocates in steady state.
Expected behavior
- Staggered/backoff restore of host-managed sessions instead of a simultaneous stampede
- Crash-looping spares should back off, not respawn hot (each cycle leaks the previous generation's fds)
- Pool-size cap and/or fd-budget awareness (a check of
kern.num_filesvskern.maxfilesbefore mass-spawning would have prevented every incident) - ENFILE from harness paths surfaced as "system file table full" rather than cascading into spurious "Not logged in" / auto-update failures
Impact
Full OS outage on a production automation machine: 2 kernel panics, ~5 hard power-cycles in one day, risk to uncommitted work in worktrees. Workaround we're using: keep the background-session roster small and clear roster.json after unclean daemon death.
We have complete raw evidence (per-second fd CSVs, lsof snapshots, panic reports, diagnostic reports, roster backup) and can provide any of it, or run instrumented repros on request.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗