Daemon self-respawn after a Homebrew cask upgrade targets the purged versioned path (ENOENT), killing every background session

Status Open
Reported on v2.1.224
Maintainer reply None cached
Activity 1 comment · opened Aug 7, 2026

Summary

When the claude-code@latest Homebrew cask is upgraded while a daemon is running, the daemon correctly detects that its binary was removed and decides to restart. But it re-executes the versioned Caskroom path it was launched from, which Homebrew has just purged, instead of the /opt/homebrew/bin/claude symlink that already points at the new version. posix_spawn fails with ENOENT, the daemon exits, and every background worker it owned is lost.

Interactive foreground sessions are unaffected (their parent is the terminal). Only background sessions die, and they die silently: the user only finds out when a session reopened from the fleet view hangs forever on Session is starting - showing its transcript until it appears.

Environment

  • macOS 26.6, arm64 (Apple Silicon)
  • Homebrew 6.0.15-130-g368c813, cask claude-code@latest
  • Claude Code 2.1.224 (failure observed on 2.1.217, 2.1.221, 2.1.223)

Logs

Three confirmed occurrences in ~/.claude/daemon.log, each right after an unattended brew upgrade:

[2026-07-23T08:58:02.281Z] [supervisor] binary at /opt/homebrew/Caskroom/claude-code@latest/2.1.217/claude was deleted (was /opt/homebrew/Caskroom/claude-code@latest/2.1.217/claude) — exiting for upgrade
[2026-07-23T08:58:02.374Z] [supervisor] shutting down (cause=upgrade, uptime=38646s, leases=2, live_workers=1)
[2026-07-23T08:58:02.410Z] [supervisor] upgrade self-respawn failed to spawn: ENOENT: no such file or directory, posix_spawn '/opt/homebrew/Caskroom/claude-code@latest/2.1.217/claude' — bg workers may be orphan-reaped ~60s after this process exits unless a client restarts the daemon (run `claude agents`)

[2026-08-05T09:01:42.732Z] [supervisor] binary at /opt/homebrew/Caskroom/claude-code@latest/2.1.221/claude was deleted (was /opt/homebrew/Caskroom/claude-code@latest/2.1.221/claude) — exiting for upgrade
[2026-08-05T09:01:42.796Z] [supervisor] shutting down (cause=upgrade, uptime=840s, leases=2, live_workers=2)
[2026-08-05T09:01:42.902Z] [supervisor] upgrade self-respawn failed to spawn: ENOENT: no such file or directory, posix_spawn '/opt/homebrew/Caskroom/claude-code@latest/2.1.221/claude' — bg workers may be orphan-reaped ~60s after this process exits unless a client restarts the daemon (run `claude agents`)

[2026-08-07T12:19:32.092Z] [supervisor] binary at /opt/homebrew/Caskroom/claude-code@latest/2.1.223/claude was deleted (was /opt/homebrew/Caskroom/claude-code@latest/2.1.223/claude) — exiting for upgrade
[2026-08-07T12:19:32.129Z] [supervisor] shutting down (cause=upgrade, uptime=66788s, leases=4, live_workers=2)
[2026-08-07T12:19:32.180Z] [supervisor] upgrade self-respawn failed to spawn: ENOENT: no such file or directory, posix_spawn '/opt/homebrew/Caskroom/claude-code@latest/2.1.223/claude' — bg workers may be orphan-reaped ~60s after this process exits unless a client restarts the daemon (run `claude agents`)

The workers are confirmed lost when the next daemon starts:

[2026-08-07T12:41:21.553Z] [bg] bg adopt: adopted=0 respawned=0 dead=2

Two more upgrade-triggered shutdowns are in the same log (2026-06-04 on 2.1.161, 2026-07-01 on 2.1.196); the 2026-07-01 one has no self-respawn failed line, so the respawn apparently won that race. The failure is timing-dependent, not systematic.

Why the target path is wrong

Homebrew relinks the binary before purging the old version. From the same upgrade run:

==> Unlinking Binary '/opt/homebrew/bin/claude'
==> Linking Binary 'claude' to '/opt/homebrew/bin/claude'
==> Purging files for version 2.1.223 of Cask claude-code@latest

So at the moment the daemon notices its binary is gone and tries to respawn, /opt/homebrew/bin/claude is already a valid symlink to the new version:

$ ls -l /opt/homebrew/bin/claude
lrwxr-xr-x  1 user  admin  56 Aug  7 14:18 /opt/homebrew/bin/claude -> /opt/homebrew/Caskroom/claude-code@latest/2.1.224/claude

The daemon has a working path available and does not use it. It re-executes process.execPath (or an equivalent captured at startup), which for a Homebrew cask install is always the version-pinned Caskroom path, i.e. exactly the path that any upgrade is guaranteed to delete.

Impact

  • All background sessions are lost, with no notification in the UI.
  • The fleet view still lists them and still offers to attach. The socket at /tmp/cc-daemon-<uid>/<daemon>/rv/<session>.sock survives with no process behind it, so the client waits forever on Session is starting.
  • Reattaching repeatedly does not help and leaves the terminal in raw mode.
  • The transcripts themselves are intact; claude --resume <session-id> still works, which is a usable workaround once you know the session is dead.

Steps to reproduce

  1. Install Claude Code via brew install --cask claude-code@latest on macOS.
  2. Start a background session and leave it running.
  3. Upgrade the cask from another shell: brew upgrade --cask claude-code@latest.
  4. Watch ~/.claude/daemon.log.

Expected: the daemon restarts on the new version and re-adopts its workers.
Actual: self-respawn failed to spawn: ENOENT, daemon exits, bg adopt: ... dead=N on the next start.

Suggested fix

Resolve the respawn target at respawn time rather than reusing the startup path. Concretely, prefer the stable launcher path when one exists (/opt/homebrew/bin/claude, /usr/local/bin/claude, or whatever which claude resolves to) and fall back to process.execPath only if that is missing. A cheap additional guard: if posix_spawn fails, retry once against the resolved launcher before giving up and reaping the workers.

It would also help to surface this to the user. Right now a lost background session is indistinguishable from a slow one in the fleet view; a session whose worker died could be marked as such instead of showing Session is starting indefinitely.

Possibly related

  • #83715 (daemon upgrade storms, self-restart on mtime)
  • #77384 (Linux: self-respawn dies in posix_spawn with EACCES after npm postinstall race)
  • #80021 (Linux: daemon self-update leaves a stub)
  • #77683 (dead background sessions cannot be cleared from the fleet view)

View original on GitHub ↗

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