Background-session daemon fd storm exhausts the system file table (ENFILE) → launchd SIGBUS → kernel panic

Status Open
Maintainer reply None cached
Activity 2 comments · opened Jul 21, 2026

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 -p sessions 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 system
  • locationd: SIGBUS KERN_MEMORY_ERROR (pagein failure)
  • ReportCrash itself: 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.json contains 10 workers → launch claude → 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.json moved 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_files vs kern.maxfiles before 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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗