fix(afk): backend-aware away-mode delivery and race-free verified daemon arm - #2
Merged
Conversation
…-119) Root cause of the missed AFK delivery under herdr, in two compounding halves: 1. The supervise-daemon's supervisor-pane discovery, startup validation, pane-gone guard, busy/composer checks, and inject submit were all raw tmux. Inside a herdr pane (no TMUX_PANE) discovery fell back to the tmux target firstmate:0, the startup probe failed, and the daemon exited 1 immediately - escalation into a herdr pane was structurally impossible even though bin/backends/herdr.sh already had the needed primitives. 2. /afk launched the daemon fire-and-forget (nohup ... >/dev/null 2>&1 &), so that startup death was invisible: no verification, exit status and stderr discarded. The daemon owns the watcher during afk, so nothing watched the fleet; the liveness beacon went stale and the crewmate's captain-relevant done: wake sat stranded in state/.wake-queue until the captain typed. Fix: - fm-supervise-daemon.sh: resolve a supervisor BACKEND alongside the target (FM_SUPERVISOR_BACKEND override; TMUX_PANE -> tmux; HERDR_ENV + HERDR_PANE_ID -> herdr; explicit FM_SUPERVISOR_TARGET alone keeps its legacy tmux meaning) and dispatch target-alive, busy, pending-input, and the verified submit through the backend layer. The tmux path runs the exact same commands as before. A failed startup validation now also drops a durable state/.subsuper-startup-failed evidence marker. The shutdown trap is installed as soon as the pidfile is published, so a stop landing during startup exits clean instead of 143. - bin/fm-afk-arm.sh (new): verified, home-scoped daemon arm mirroring fm-watch-arm.sh's honest contract (started/healthy/FAILED, non-zero on FAILED, stderr captured to state/.supervise-daemon.err). It stays attached to the daemon it starts, so a later daemon death completes the harness-tracked task and re-notifies firstmate. --stop signals only the pid this home's pidfile and lock agree on - never pkill. - afk SKILL.md + AGENTS.md away-mode stub: launch/stop through fm-afk-arm.sh, never nohup fire-and-forget; surface the new startup- failed marker in the catch-up. - tests/fm-afk-daemon-herdr-delivery.test.sh (new): isolated FM_STATE_OVERRIDE homes, fake herdr CLI, failing tmux stub. Proves the old failure (daemon start under herdr markers), end-to-end herdr delivery of a captain-relevant status as one sentinel digest with the wake enqueued and not stranded, the loud startup failure with durable evidence, and the arm's FAILED/started/healthy/stop contract.
The daemon's supervisor_target_alive herdr arm went through fm_backend_capture -> target_ready -> server_ensure, which auto-starts a headless herdr server and blocks up to 10s, and cost a 200-line pane read per ~1s main-loop iteration. Replace it with fm_backend_herdr_pane_alive: one read-only 'herdr pane get' call that fails when the pane or server is gone and never resurrects a deliberately stopped server. tmux arm unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Fix the AFK Herdr branch readiness race: fm-afk-arm.sh must not report daemon: started or healthy until the supervise daemon has completed supervisor backend/target validation and published an explicit home-scoped readiness marker agreeing with pidfile, lock, and live pid. Startup validation failures must report daemon: FAILED with useful evidence, clean shutdown must remove readiness state, home scoping must be preserved, pkill must not be used, and Herdr readiness/liveness probes must stay read-only without resurrecting a stopped Herdr server. Keep the upstream kunchenguid/firstmate PR paused; do not open an upstream PR yet. It is acceptable for no-mistakes to update its existing fork-side validation branch or PR if its pipeline requires that.
What Changed
bin/backends/herdr.sh) that never resurrects a stopped herdr server.bin/fm-afk-arm.sh, a verified home-scoped daemon arm that reportsdaemon: startedorhealthyonly after the daemon publishes a readiness marker agreeing with the pidfile, lock owner, and live pid — closing the startup readiness race — and reportsdaemon: FAILEDwith captured stderr evidence (the earlier undefinederr_tailcall now usesfailure_evidence) on startup validation failure;--stopremoves pid and readiness state cleanly, with nopkillanywhere.tests/fm-afk-daemon-herdr-delivery.test.shand extended herdr backend tests, and synced the afk skill, architecture, configuration, and scripts docs to the readiness-gated arm flow.Risk Assessment
✅ Low: The only confirmed defect from round 1 (undefined err_tail) was fixed exactly as instructed; the readiness/lock/pidfile ordering, read-only herdr probes, and home-scoped stop all verify correct, and the change ships a dedicated regression test suite.
Testing
Ran the full baseline test suite plus the dedicated afk-daemon-herdr-delivery suite (all green), then manually demonstrated the readiness contract end-to-end in an isolated state home: started is only reported once the readiness marker agrees with pidfile/lock/live pid, re-arm reports healthy, --stop cleanly removes readiness state, and a startup validation failure produces a loud daemon: FAILED with evidence; transcript captured as an artifact.
Evidence: AFK arm readiness lifecycle transcript (started/healthy/stopped/FAILED with state-file evidence)
=== 1. arm inside a herdr pane === arm: daemon: started pid=77207 readiness marker: .supervise-daemon.ready = 77207 pidfile: .supervise-daemon.pid = 77207 lock owner: .supervise-daemon.lock/pid = 77207 === 2. second arm === arm: daemon: healthy pid=77207 === 3. --stop === arm: daemon: stopped pid=77207 attached arm final output: daemon: stopped (clean shutdown) .supervise-daemon.pid: removed .supervise-daemon.ready: removed === 4. failure case === arm: daemon: FAILED - exited rc=1 during startup (see .../.supervise-daemon.err) arm: ...supervisor target firstmate:0 ... does not resolve to a live pane exit code: 1Evidence: Demo script used for the manual verification
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-afk-arm.sh:160-err_tailis called but never defined; the only evidence helper isfailure_evidence. When a started daemon dies with non-zero rc, the arm printserr_tail: command not foundinstead of the captured stderr evidence — the exact evidence this fix exists to surface. Replace withfailure_evidence(ortail -5 "$ERRFILE").bin/fm-afk-arm.sh:142-: > "$ERRFILE"truncates the previous run's daemon stderr before each new launch, discarding prior failure evidence; the durable state/.subsuper-startup-failed marker still survives, so this is an acceptable tradeoff worth noting.bin/fm-supervise-daemon.sh:503- On herdr,supervisor_input_pendingalways reports not-pending (no composer read primitive), leaving a bounded captain-mid-typing race; deliberately accepted and documented, with the typed-baseline submit verification as the guard.🔧 Fix: replace undefined err_tail with failure_evidence in fm-afk-arm
1 info still open:
bin/fm-afk-arm.sh:124- The arm path only skips forking when a daemon is fully healthy; a daemon still in its pre-readiness startup window (addressable but not ready, e.g. launched by a concurrent arm) causes this arm to fork a doomed duplicate that loses the singleton lock and exits 1. The confirm loop handles the common case (reportsdaemon: healthyonce the winner publishes readiness), but if the winner dies before this arm's child, the arm reports FAILED with 'another daemon already running' evidence — slightly confusing but honest and self-correcting on re-arm.✅ **Test** - passed
✅ No issues found.
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"Baseline configured command (alltests/*.test.shunder tmux) ran green before this validationbash tests/fm-afk-daemon-herdr-delivery.test.sh— herdr delivery, loud startup failure, arm FAILED honesty, and started→healthy→stopped lifecycle all passManual end-to-end demo (evidence demo.sh): armed the daemon in an isolated FM_STATE_OVERRIDE home with a fake herdr pane and no tmux server, verifieddaemon: startedonly appears with .supervise-daemon.ready == pidfile == lock owner pid, second arm reportshealthywithout duplicating,--stopremoves pid and readiness files and the attached arm reports clean shutdown, and a no-endpoint home yieldsdaemon: FAILED(exit 1) with the supervisor-target evidence in .supervise-daemon.errgrep -n pkill bin/fm-afk-arm.sh bin/fm-supervise-daemon.sh— pkill appears only in a comment warning against its useFull suite re-run (for t in tests/*.test.sh) — all green, worktree left clean✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.