claude --bg daemon idle-exit (5s) races client handshake → "socket missing"
Version: Claude Code 2.1.139 + 2.1.150 · Linux (Ubuntu, Hetzner) · Node v22
What I expected
claude --bg "<prompt>" invoked from a long-quiet shell (e.g. a systemd timer firing every 6h) reliably spawns a background session and prints backgrounded · <short_id>, even when the supervisor daemon is not currently running.
What actually happens
When the daemon is cold or has just idle-exited, claude --bg occasionally exits non-zero with:
Starting background service…
Couldn't reach the background service (socket missing) — run 'claude daemon status'
No session ID is emitted. Subsequent calls within a few seconds usually succeed.
Reproduction
claude --bg "<any prompt>"to start the daemon, let it spawn the session.- Wait until the daemon idle-exits (≈5s after the last client disconnects). Confirm in
~/.claude/daemon.log:
````
[supervisor] idle 5s with no clients — exiting
- Fire another
claude --bg "<prompt>".
Most of the time it works. Occasionally the second call races the supervisor's idle-exit and prints the socket-missing error. Our daemon log on a failing call shows:
[2026-05-29T07:40:39.563Z] [supervisor] ─── daemon start ─── pid=2454855 origin=transient
[2026-05-29T07:40:39.616Z] [supervisor] workers=0
[2026-05-29T07:40:39.658Z] [bg] bg adopt: adopted=0 dead=1
[2026-05-29T07:40:44.620Z] [supervisor] idle 5s with no clients — exiting
[2026-05-29T07:40:44.622Z] [supervisor] shutting down
[2026-05-29T07:40:44.987Z] [supervisor] ─── daemon start ─── pid=2454889 origin=transient
[2026-05-29T07:40:45.038Z] [supervisor] workers=0
[2026-05-29T07:40:50.042Z] [supervisor] idle 5s with no clients — exiting
So the supervisor started twice and idle-exited twice within 11s — the CLI client never managed to connect/handshake before each fresh daemon exited.
Impact
We run claude --bg from a systemd timer every 6h to fire an autonomous agent. The race hits about 1 cron tick per 24h, dropping the agent for that 6h window. The next cycle works fine.
Attempted workarounds that don't work
- Retry on failure — masks intermittent failure but does not address the race.
claude agents >/dev/nullwarm-up immediately beforeclaude --bg— only works when the gap between the two calls stays under the 5s idle window. When system load grows the gap past 5s, the warmed daemon idle-exits before--bgcan connect and the race re-fires anyway. Tested and reverted.claude daemon logs— does not hold an open client connection; just streams the log file and exits.
The only locally-reliable workaround we've found is a continuous heartbeat loop (while true; do claude agents; sleep 3; done) wrapping the --bg call to guarantee a client is always within the 5s idle window. That's wasteful and shouldn't be necessary.
Suggested upstream fix directions
Any one of these would close the race:
- Lengthen / configurable daemon idle timeout. 5s is aggressive for a daemon that's expected to handle on-demand spawns from cold. A 30s default plus a
--json-pathconfig knob (idle_timeout_ms) would eliminate the race in practice. - Delay idle-exit until the spawning client has handshaken at least once. When the CLI forks the daemon, it intends to immediately connect — the supervisor could wait for that initial client before starting its idle countdown.
- CLI-side retry on EAGAIN/socket-missing. When the CLI sees a missing/closed socket immediately after the daemon-start call it issued, retry the connect with a short backoff. Already-correct semantics for the same-process spawn case.
Happy to test patches.