Skip to content

fix: restore Herdr Claude away-mode delivery - #116

Merged
withally merged 6 commits into
mainfrom
fm/fm-afk-herdr-claude-live-guard-2-1-263-f1
Sep 8, 2026
Merged

fix: restore Herdr Claude away-mode delivery#116
withally merged 6 commits into
mainfrom
fm/fm-afk-herdr-claude-live-guard-2-1-263-f1

Conversation

@withally

@withally withally commented Sep 8, 2026

Copy link
Copy Markdown
Owner

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

  • Expanded Claude composer/footer parsing for permission-mode variants and Claude 2.1.263 titled-rule layouts, including rendered-busy spinner detection.
  • Hardened away-mode turn-end supervision to verify daemon ownership, relaunch dead daemons through the lifecycle owner, and preserve wedge blocking when recovery lacks a heartbeat.
  • Added Claude 2.1.263 fixtures and auto/bypass live coverage, plus portable regressions and runtime/test-stability documentation updates.

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

# AFK Herdr + Claude live-guard test evidence

Date: 2026-09-08 Asia/Hong_Kong.

Target: `7397e53296fdecf5d4d0c447fcf793ad0a748a8e`.

The provided baseline `bin/fm-test-run.sh --changed --exclude-family real-herdr-gated` had already completed successfully before this test phase.

## Focused portable behavior

Commands:

`` `text
bash tests/fm-composer-lib.test.sh
bash tests/fm-turnend-guard.test.sh
`` `

Relevant observable results:

`` `text
ok - fm_claude_current_footer_busy: the footer belongs to the selected composer boundary
ok - fm_claude_current_footer_busy: Claude 2.1.263 titled-rule composers and permission-mode footers stay readable
ok - fm-turnend-guard: away mode attempts relaunch and blocks if dead-daemon recovery fails
ok - fm-turnend-guard: dead away-daemon ownership is checked before a live watcher can allow
ok - fm-turnend-guard: a turn boundary relaunches a dead away daemon through fm-afk-launch.sh
ok - fm-turnend-guard: dead-daemon relaunch requires a post-launch heartbeat
`` `

## Real Herdr + Claude 2.1.263, auto mode

Command:

`` `text
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=auto bin/fm-test-run.sh tests/fm-afk-herdr-claude-busy-guard-live-e2e.test.sh
`` `

The named non-default lab run showed an idle native state with an empty rendered composer, then a genuine foreground spinner and preserved human text:

`` `text
verdict: idle-post-afk agent_status=idle composer=empty pane_is_busy_rc=1 broad_match_rc=1 scoped_match_rc=1 subcause=idle native-state=idle matched-row=none
⏵⏵ auto mode on · 1 shell · ← 1 agent · ↓ to manage
ok - real Herdr 0.8.2 + Claude 2.1.263 (Claude Code) (auto mode): native idle with rendered-idle empty composer submits once
verdict: active-foreground native-state=working subcause=rendered-busy matched-row=✶ Caramelizing… (15s · ↓ 127 tokens)
ok - real Herdr 0.8.2 + Claude 2.1.263 (Claude Code) (auto mode): rendered-busy and pending-composer deferrals preserve human text
evidence: permission-mode=auto native=idle rendered=idle composer=empty stable-footer-composer=3 delivery-ms=4125 delivery-bound-ms=6000 delivered_once=1 rendered-busy=1 native-state=working=1 composer=pending=1
FM_TEST_END 2026-09-08T02:32:18Z tests/fm-afk-herdr-claude-busy-guard-live-e2e.test.sh exit=0 duration_ms=71096 gate_skip=false
`` `

## Real Herdr + Claude 2.1.263, bypass-permissions mode

Command:

`` `text
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
`` `

The first attempt was a non-reproducible live harness failure: Claude rendered the bypass footer and answered `/afk` with the test ACK, but did not enter the afk skill lifecycle, so no daemon record appeared.

The identical rerun passed the full end-to-end contract:

`` `text
verdict: idle-post-afk agent_status=idle composer=empty pane_is_busy_rc=1 broad_match_rc=1 scoped_match_rc=1 subcause=idle native-state=idle matched-row=none
⏵⏵ bypass permissions on · 1 shell · ← 1 agent · ↓ to manage
ok - real Herdr 0.8.2 + Claude 2.1.263 (Claude Code) (bypass permissions): native idle with rendered-idle empty composer submits once
verdict: active-foreground native-state=working subcause=rendered-busy matched-row=✻ Actioning… (12s · ↓ 159 tokens)
ok - real Herdr 0.8.2 + Claude 2.1.263 (Claude Code) (bypass permissions): rendered-busy and pending-composer deferrals preserve human text
evidence: permission-mode=bypassPermissions native=idle rendered=idle composer=empty stable-footer-composer=3 delivery-ms=3979 delivery-bound-ms=6000 delivered_once=1 rendered-busy=1 native-state=working=1 composer=pending=1
FM_TEST_END 2026-09-08T02:37:13Z tests/fm-afk-herdr-claude-busy-guard-live-e2e.test.sh exit=0 duration_ms=90473 gate_skip=false
`` `

Both successful live runs used generated `fm-lab-*` sessions and the runner reported `gate_skip=false`.
- Outcome: ⚠️ 1 warning across 2 runs (1h27m4s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 4 issues (1 error, 3 warnings)
  • 🚨 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 as Deployment… 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, while fm-afk-launch.sh returns 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 invokes fm-afk-launch.sh start at line 239, but docs/turnend-guard.md:51 still 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.sh instead 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 or git rev-parse --show-toplevel path in both commands.
  • ⚠️ tests/fm-afk-herdr-claude-busy-guard-live-e2e.test.sh:521 - The live test explicitly accepts native status done as a successful idle state, but line 521 and the pass messages hardcode native=idle. A valid done result therefore produces evidence that misstates the observed Herdr state; record the raw status or label the field as normalized idle/done.
⚠️ **Test** - 1 warning
  • 🚨 tests failed with exit code 1
  • 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 /afk without 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-gated
  • bin/fm-test-run.sh --changed --exclude-family real-herdr-gated (provided baseline)
  • bash tests/fm-composer-lib.test.sh
  • bash tests/fm-turnend-guard.test.sh
  • 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=auto bin/fm-test-run.sh tests/fm-afk-herdr-claude-busy-guard-live-e2e.test.sh
  • 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 (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=all
  • Verified 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.

@withally withally changed the title fix(supervision): recover Claude away-mode delivery fix: restore Herdr Claude away-mode delivery Sep 8, 2026
@withally
withally merged commit 24b92af into main Sep 8, 2026
15 checks passed
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