Skip to content

fix(daemon): restart recovers from a session-less zombie container instead of blocking - #56

Merged
rappdw merged 1 commit into
mainfrom
fix/daemon-zombie-restart
Jul 17, 2026
Merged

fix(daemon): restart recovers from a session-less zombie container instead of blocking#56
rappdw merged 1 commit into
mainfrom
fix/daemon-zombie-restart

Conversation

@rappdw

@rappdw rappdw commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Fixes the "restart error only happens when a prior exited-but-alive container is present" case.

The bug

A daemon container can be up but with its agent session dead (a zombie) — the agent exits, but the container's PID 1 (tail -f /dev/null) keeps it running, and it lingers until the #47 supervisor's ~60s watch-loop reaps it (or indefinitely if the supervisor died). Both restart-decision paths keyed off container existence alone:

  • --start idempotency (sandy:3567) → "Daemon session already running" + exit 0
  • bare sandy DEC-B (sandy:5373) → "A daemon session is already running" + exit 1

So a zombie blocked the restart even though the session was dead.

The fix

Refine D9 "container-as-truth" → "container with a LIVE session as truth." Both checks now probe the inner session (docker exec … tmux has-session) with a short 5×1s mid-startup retry (so a still-launching container isn't misread and reaped out from under a concurrent start). A dead-session zombie is reaped via "$0" --stop and the op proceeds fresh — --start launches a new session, bare sandy falls through to interactive.

This is defensive — it makes restart robust regardless of why the zombie exists (supervisor died, non-TTY sandy-ui launch, a restart racing the teardown window).

Tests / docs

  • run-tests.sh §73 — structural: both checks probe has-session + reap via $0 --stop.
  • acceptance-daemon.sh §6.5 — real-docker end-to-end: tmux kill-server to make a zombie → assert --start reaps it and replaces the container with a fresh live session (and §7 teardown re-points at the new one).
  • CLAUDE.md — D6 refinement + DEC-B update.

Note: the separate question of why an agent exits immediately in a given workspace (unconfirmed; likely a non-TTY credential/approval drop on the sandy-ui launch path) is not addressed here — that's still being diagnosed.

🤖 Generated with Claude Code

…stead of blocking

Hit via sandy-ui: the agent exits but the daemon container stays up (container
alive, inner tmux session dead) — a zombie. On the next launch, both the
--start idempotency check and the bare-sandy DEC-B check keyed off container
EXISTENCE alone (`docker ps -q --filter label=sandy.daemon=true`), so the zombie
read as "already running" and wedged the user out of the workspace.

Refine D9 "container-as-truth" to "container WITH A LIVE SESSION as truth": both
checks now probe the inner session (`docker exec … tmux has-session`, with a
5×1s mid-startup retry so a container that's merely still-launching isn't
misread and reaped out from under a concurrent start). A dead-session zombie is
reaped via `"$0" --stop` and the operation proceeds fresh — --start launches a
new session, bare sandy falls through to interactive.

Defensive: makes restart robust regardless of WHY the zombie exists (supervisor
died, non-TTY sandy-ui launch, a restart racing the ~60s #47 teardown window).
The separate question of why an agent exits immediately in a given workspace is
unaffected by this.

Tests: run-tests.sh §73 (structural — both checks probe has-session + reap via
$0 --stop) and acceptance-daemon.sh §6.5 (real-docker end-to-end: kill the inner
tmux session → assert --start reaps the zombie and replaces it with a fresh live
session). Docs: CLAUDE.md D6 refinement + DEC-B.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rappdw
rappdw merged commit 3f13bc6 into main Jul 17, 2026
2 checks passed
@rappdw
rappdw deleted the fix/daemon-zombie-restart branch July 17, 2026 03:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant