fix: restore Herdr Claude away-mode delivery - #116
Merged
Conversation
…nd footer matching
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 2026-09-07 away-mode delivery failure on Claude Code 2.1.263 with the Herdr backend. In bin/fm-composer-lib.sh, recognize the Claude permission footer family through one owned mode alternation (at least bypass permissions, auto mode, accept edits, and plan mode), existing optional suffixes and continuation rows, the bare idle footer, and the 2.1.263 titled-rule composer so idle is empty and the captured spinner shape is rendered-busy. Add portable public-function regressions using real captured busy bytes and a read-only idle capture. At the away-mode turn boundary, check the identity-backed daemon lock and use the existing lifecycle owner to relaunch a dead daemon where a hook can host it, otherwise raise the documented wedge alarm; add a portable dead-lock regression. Refresh live proof in the isolated named Herdr lab against installed Claude 2.1.263 in both auto and bypass modes, repair the test contract if its foreground-settle assumption is wrong, and record dated per-harness results in docs/verification/runtime-backends.md. Preserve one owner per contract, one sentence per Markdown line, shellcheck cleanliness, and full bin/fm-lint.sh validation. Keep the existing pull request #116 and have no-mistakes push and attest that same PR; do not open a second PR or merge it.
What Changed
Risk Assessment
🚨 High: The new away-mode recovery path can still allow a turn without proving that the replacement daemon owns active supervision.
Testing
Captain, the provided baseline was successful; focused portable composer and turn-end tests passed; fresh Herdr 0.8.2 + Claude Code 2.1.263 live E2E passed in auto and bypass-permissions modes on successful runs, proving idle/empty delivery, rendered-busy deferral, and pending human-text preservation. Evidence is recorded in the linked CLI transcript; linters/static analysis were not run because this assigned phase forbids them.
Evidence: AFK Herdr + Claude live-guard evidence
Source: AFK Herdr + Claude live-guard evidence
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-turnend-guard.sh:200- The authoritative intent requires 'At the away-mode turn boundary, check the identity-backed daemon lock and use the existing lifecycle owner to relaunch a dead daemon where a hook can host it, otherwise raise the documented wedge alarm.' The new check at lines 200-201 is after the existing watcher-health early exit at lines 187-189. If the daemon dies while its child watcher remains live with a fresh beacon, the guard exits at line 188, never checks the dead daemon or raises the alarm. Check AFK daemon ownership before allowing the generic watcher path.bin/fm-turnend-guard.sh:203- The relaunch path reuses FM_SUP_WATCHER_FRESH calculated before the launcher runs. The lifecycle owner considers readiness established when the new daemon lock is live, before its first watcher heartbeat. With a fresh pre-crash beacon, a replacement that dies during startup can therefore pass line 203 and allow a blind turn; with a stale pre-crash beacon, a healthy replacement remains blocked. Require a post-relaunch heartbeat or equivalent readiness proof before allowing the turn.bin/fm-composer-lib.sh:318- The updated Claude footer regex makes the duration optional, so a structurally adjacent ordinary row such asDeployment…can match as rendered-busy when no permission or idle footer is recognized. Herdr then defers delivery on an idle pane. Keep the duration requirement while allowing the new second-duration shape, or constrain the matcher to the verified spinner form.🔧 Fix: Captain: fixed away ordering, heartbeat proof, and footer matching
4 issues (1 error, 3 warnings) still open:
bin/fm-turnend-guard.sh:198- The relaunch readiness proof at lines 198-207 only requires the identity-backed daemon lock plus any newer, fresh mtime on.last-watcher-beat. A documented orphaned watcher can still advance that file after its daemon dies, whilefm-afk-launch.shreturns once the replacement lock is live, before its watcher starts. The guard can therefore allow a turn while the replacement daemon is stalled before supervision. Revert the fixer-added mtime-only proof to a supported owner-bound readiness proof, or authorize a lifecycle generation/receipt protocol.docs/turnend-guard.md:51- The new branch invokesfm-afk-launch.sh startat line 239, butdocs/turnend-guard.md:51still says a home with no daemon and no watcher blocks exactly as before. The runbook and adjacent comment omit the new recovery attempt and its readiness-failure alarm path; document the launch, readiness check, and final wedge behavior.docs/verification/runtime-backends.md:783- The newly recorded reproduction commands hardcode~/Projects/firstmate/bin/fm-herdr-lab.shinstead of resolving the helper from the checkout. They can run a different checkout or fail when followed from this worktree, so the dated proof is not reproducible from the source it documents. Use a repository-relative orgit rev-parse --show-toplevelpath in both commands.tests/fm-afk-herdr-claude-busy-guard-live-e2e.test.sh:521- The live test explicitly accepts native statusdoneas a successful idle state, but line 521 and the pass messages hardcodenative=idle. A validdoneresult therefore produces evidence that misstates the observed Herdr state; record the raw status or label the field as normalized idle/done.bin/fm-test-run.sh --changed --exclude-family real-herdr-gated🔧 Fix: Stabilized concurrent wake and Pi rendering tests
1 warning still open:
tests/fm-afk-herdr-claude-busy-guard-live-e2e.test.sh:431- The first bypass-permissions live run intermittently failed because Claude answered/afkwithout entering the afk lifecycle; the identical rerun passed end to end. This is a live Claude skill-dispatch flake, not a reproduced product failure.bin/fm-test-run.sh --changed --exclude-family real-herdr-gatedbin/fm-test-run.sh --changed --exclude-family real-herdr-gated(provided baseline)bash tests/fm-composer-lib.test.shbash tests/fm-turnend-guard.test.shHERDR_LAB_HELPER=~/.no-mistakes/worktrees/37852af5566c/01M1Z6QTKQRP9XRY4HBQY9P42V/bin/fm-herdr-lab.sh FM_AFK_HERDR_CLAUDE_LIVE=1 FM_AFK_HERDR_CLAUDE_PERMISSION_MODE=auto bin/fm-test-run.sh tests/fm-afk-herdr-claude-busy-guard-live-e2e.test.shHERDR_LAB_HELPER=~/.no-mistakes/worktrees/37852af5566c/01M1Z6QTKQRP9XRY4HBQY9P42V/bin/fm-herdr-lab.sh FM_AFK_HERDR_CLAUDE_LIVE=1 FM_AFK_HERDR_CLAUDE_PERMISSION_MODE=bypassPermissions bin/fm-test-run.sh tests/fm-afk-herdr-claude-busy-guard-live-e2e.test.sh(initial attempt)HERDR_LAB_HELPER=~/.no-mistakes/worktrees/37852af5566c/01M1Z6QTKQRP9XRY4HBQY9P42V/bin/fm-herdr-lab.sh FM_AFK_HERDR_CLAUDE_LIVE=1 FM_AFK_HERDR_CLAUDE_PERMISSION_MODE=bypassPermissions bin/fm-test-run.sh tests/fm-afk-herdr-claude-busy-guard-live-e2e.test.sh(identical successful repeat)git status --short --untracked-files=allVerified evidence transcript at~/.no-mistakes/evidence/01M1Z6QTKQRP9XRY4HBQY9P42V/afk-herdr-claude-live-guard-2026-09-08.md✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.