fix(bin): make supervision recovery owner-aware and durable - #2392
Open
tiago-peixoto wants to merge 7 commits into
Open
fix(bin): make supervision recovery owner-aware and durable#2392tiago-peixoto wants to merge 7 commits into
tiago-peixoto wants to merge 7 commits into
Conversation
* docs(stow): generalize read-before-write in the public stow skill (kunchenguid#2091) The public installer-facing stow skill scoped its classify-then-replace discipline to TODO/BACKLOG items only, so findings routed to a memory file had no stated rule against a blind append or a wholesale overwrite. Step 6 now classifies every finding against the destination's current contents as new, duplicate, superseding, or obsolete, and states the considered replacement each classification implies. The outcomes follow the tiered-memory contract already in the file: an obsolete entry is refreshed, archived, or replaced in a way that preserves its fact, a duplicate folds into the entry that already carries it, and a superseded body worth keeping leaves through step 7's existing exits rather than a second recovery mechanism. * fix: resurface durable supervision work after re-arm (kunchenguid#2065) * fix(watcher): resurface durable work after downtime * no-mistakes(review): Make watcher rearm recovery durable and cursor-safe * no-mistakes(review): Persist safe recovery markers across migration lock recovery * no-mistakes(review): Retain stale lock when recovery marker publication fails * no-mistakes(review): Preserve delivery-gap recovery and quarantine malformed markers * no-mistakes(review): Serialize recovery consumption and report acknowledgment failures * no-mistakes(review): Centralize recovery publication before clearing watcher evidence * no-mistakes(review): Guarantee recovery evidence across queue and lock handoffs * no-mistakes(review): Publish recovery evidence before durable wake commits * no-mistakes(review): Replace recovery marker Perl dependency with Node * no-mistakes(review): Keep interrupted wakes durable until handling acknowledgment * no-mistakes(review): Add post-handling durable wake acknowledgements * no-mistakes(review): Enforce post-handling acknowledgement across recovery and AFK return * no-mistakes(review): Bind wake acknowledgements to recovery generations * no-mistakes(review): Align wake regressions with generation-bound acknowledgements * no-mistakes(document): Document durable re-arm recovery semantics * no-mistakes(lint): Resolve ShellCheck warnings in recovery and watcher tests * no-mistakes: apply CI fixes * test(watcher): assert post-handling wake replay * no-mistakes(review): Prevent successor loops and adopt legacy wake generations * no-mistakes(review): Rearm durable wakes without recursive successor recovery * no-mistakes(review): Align recovery tests with handling marker state * no-mistakes(review): Delay handling transition until successor launch is established * no-mistakes(review): Confirm wake handling only after successful prompt delivery * no-mistakes(review): Acknowledge AFK wakes only after evidence publication * no-mistakes(review): Prevent AFK wake loss before post-handling acknowledgement * no-mistakes(document): Document durable wake acknowledgement semantics * no-mistakes(lint): Suppress false positive for recovery action output * no-mistakes: apply CI fixes * no-mistakes: apply CI fixes * ci: measure Herdr automation on Windows runners (kunchenguid#2100) * ci: add Windows Herdr automation spike * ci: run Windows spike on its pull request * fix: wait for Windows Herdr command output * fix: run ANSI probe in pane shell * ci: keep Windows Herdr spike manually triggered * docs: clarify Windows Herdr spike verdict * fix: support Pi 0.83 worker launches --------- Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
…ged wake recovery
tiago-peixoto
force-pushed
the
fm/firstmate-stop-guard-starves-autoarm
branch
from
August 14, 2026 18:50
6a19ce3 to
c47f190
Compare
Author
|
For upstream context: this change merged on the personal fork on 2026-08-14 through tiago-peixoto#5, with all 13 checks green, and it has run live in the primary home since that merge. |
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 Claude primary-session supervision failure observed on 2026-08-14, where work was in flight, no watcher was live, and the turn-end guard could repeat forever while the Stop auto-arm did not claim recovery. First add a durable, bounded, self-trimming, best-effort state trace that records every auto-arm entry and selected pre-claim gate without changing hook output, status, or failure behavior. Reproduce only in a throwaway home with real Claude, let the evidence choose among sibling starvation, the foreign live session-lock owner gate, and discarded asyncRewake, and deliberately seek falsification: no entry on the blocked Stop would falsify the identity-gate diagnosis, while an owner claim without delivered Stop feedback would support discarded rewake. The evidence showed the hook ran and took gate-live-session-owner, while lock-owner asyncRewake worked, so preserve the identity gate and its prevention of two sessions arming one home; do not weaken the guard, arm from the read-only session, reorder hooks, or teach the falsified short-circuit mechanism. Align the guard with session ownership so the read-only competing session can finish and the lock-owning session remains the sole mutable supervision owner and restores supervision on its next Stop. Add a portable real-process regression and an env-gated real-Claude live guard, and prove both RED against unfixed code and green after the fix. After two genuinely identical guard blocks, terminate the exchange in exactly one captain-facing question, reset the identical-block count when evidence changes, and suppress repeated escalation. Update the authoritative continuity, turn-end, operating-protocol, volatile-state, and dated verification records, including exact commands and output; keep the private learning corrected to the proven mechanism. Require bin/fm-lint.sh and documentation checks to pass.
What Changed
--tui-moderemoval, and add a manual Windows Herdr measurement workflow with expanded regression coverage and documentation.Risk Assessment
✅ Low: Captain, the final change consistently uses one supervision snapshot, closes the false-X-mode race, and leaves the durable ownership and queue-delivery invariants intact.
Testing
Focused portable suites and a credentialed two-session Claude 2.1.232 flow passed; isolated portable and live ownership counterfactuals failed at the intended symptom, reviewer-visible logs were captured, and all transient test directories were removed. Lint, documentation checks, push, PR, and CI were not run because this assigned phase was test-only.
Evidence: Portable ownership-gate RED
Evidence: Portable auto-arm green
Evidence: Portable turn-end guard green
Evidence: Real-Claude ownership-gate RED
Evidence: Hardened real-Claude green
Evidence: Concise live behavior evidence
Claude 2.1.232: competing session returned COMPETING_READ_ONLY after gate-live-session-owner; owner Stop hook exited 2 with the watcher wake, produced OWNER_RECOVERED, claimed exactly one arm cycle, and recorded outcome=rewake.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
⏭️ **Rebase** - skipped
.agents/skills/afk/SKILL.md- branch carries 1 commit(s) that exist on your local main branch but were never pushed to origin/main; rebasing would bundle this unrelated work (171 file(s)) into the PR:Push main to origin, or rebase your branch onto origin/main, before gating.
🔧 **Review** - 6 issues found → auto-fixed (4) ✅
bin/fm-wake-lib.sh:428- A recovery acknowledgement can livelock:_fm_recovery_marker_publishalways creates a new generation, so if the watcher closes while generation A is being handled, downtime publication rotates it to B;fm-wake-drain.shthen rejects the exact acknowledgement for A before consuming its queue rows. The marker remains pending and later arms repeatedly re-announce the same recovery instead of supervising. Preserve an outstanding generation during republication and consume rows through the acknowledged sequence even when the episode generation has advanced.bin/fm-turnend-guard.sh:495- The required behavior says to “terminate the exchange in exactly one captain-facing question” after two identical blocks. The second block instructs the model to escalate the choice to the captain, while the next unchanged Stop emits another captain-facing question here; a compliant model therefore asks twice. Choose one question owner—either make the second block terminal, or let the model ask and allow the subsequent Stop silently.bin/fm-turnend-guard.sh:226- The requirement says to reset after evidence changes and count only “genuinely identical” blocks, but this signature records only aggregate counts and modes. If task A completes and task B starts before the next Stop, the signature remains unchanged and the new state inherits A’s block count. Include a stable fingerprint of the actual task and process-source identities, preferably from the shared supervision-status boundary while excluding volatile age fields.docs/verification/supervision.md:279- The intent requires both regressions to be proven RED against unfixed code and green after the fix, with dated records containing exact commands and output. The changed verification record documents only successful live, lint, documentation, and portable-suite runs; it contains no unfixed RED command/output for either the portable real-process regression or the real-Claude guard. Add that evidence or obtain explicit approval to omit it.bin/fm-spawn.sh:1074- Removing--tui-mode regularunconditionally supports Pi versions that removed the option, but re-enables configured fullscreen mode on older supported Pi executables that still advertise it; fullscreen scrollback can bury steers and make supervision state appear stale. Resolve the selected executable once, probe its--help, and pass the flag only when that exact executable supports it..pi/extensions/fm-primary-pi-watch.ts:253- Recovery confirmation runs synchronously without a timeout. If a live but stalled process owns.watcher-down.lock,--handling-deliveredwaits indefinitely and freezes the Pi event loop immediately after wake delivery; the OpenCode sibling has the same path. Bound the child execution or make it asynchronous, leaving the durable recovery marker pending when confirmation fails.🔧 Fix: Fix guard escalation and evidence fingerprinting
2 errors still open:
bin/fm-turnend-guard.sh:223- The accepted criterion says to “reset the identical-block count when evidence changes,” but this signature usesFM_SUP_*values sampled once at line 165, before the configurable auto-arm wait. A process-source runner can terminal-retire its.sourceduring that wait while the sibling auto-arm observes no remaining need and makes no claim; on the second attempt the guard still compares source A’s stale fingerprint, reaches count 2, and emits the captain question despite changed evidence. Refresh shared supervision status at the block-accounting boundary and pass/reset when the need disappeared before permitting terminal escalation.docs/turnend-guard.md:71- The accepted clarification requires the second identical observation to be terminal, with the guard owning the only question and subsequent unchanged Stops passing silently, and the intent requires authoritative turn-end and operating-protocol records to be updated. These changed docs still say the second banner tells the model to ask and a later Stop emits the question;docs/supervision-protocols/claude.md:15repeats that obsolete model-owned sequence. Update both authoritative documents to match the implemented second-Stop ownership rule.🔧 Fix: Refresh guard evidence and escalation documentation
1 error still open:
bin/fm-turnend-guard.sh:476- The refreshed predicate can incorrectly declare recovery complete on the supported terminal process-source path.fm-procevent.shappends the terminal result to.wake-queuebefore retiring its.source; if that occurs during this wait, auto-arm takesgate-no-supervisionand this branch passes becauseFM_SUP_QUEUE_PENDINGis excluded fromFM_SUP_NEEDED. With no watcher, the actionable result remains unsurfaced until unrelated activity. Make the shared supervision-needed boundary include pending wake delivery, and update the new regression to retire through the real process path or seed its durable wake before asserting the outcome.🔧 Fix: Keep queued wake delivery under supervision
1 warning still open:
bin/fm-guard.sh:206- The widened shared predicate is not consumed atomically here:neededcan be true solely because line 150 observed a queued wake, but line 166 re-reads the queue separately. If an acknowledgement drains the queue between those reads and no task, source, or Relay poll remains, this changed branch falls through to the X-mode message and claims a false watcher-down episode. CopyFM_SUP_QUEUE_PENDINGalongside the other shared status fields instead of re-reading the file.🔧 Fix: Use one supervision snapshot for queue warnings
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
claude --version→2.1.232 (Claude Code)tests/fm-claude-stop-autoarm.test.shtests/fm-turnend-guard.test.shThrowaway final-tree portable counterfactual with only the foreign-live-owner exemption removed;tests/fm-turnend-guard.test.shfailed at the intended trapped-session regressionFM_CLAUDE_LIVE_E2E=1 tests/fm-claude-stop-autoarm-live-e2e.test.shThrowaway final-tree real-Claude counterfactual with only the foreign-live-owner exemption removed; the live test failed at the intended blind-turn guard trapStartup-only and real-transcriptjqchecks proving the hardened assertion rejects startup prose and accepts an actual Stop exit-2 response plusOWNER_RECOVEREDTwo initial whole-parent rollback controls were diagnosed and discarded because later queued-wake behavior made them fail before the ownership regressiongit status --short --untracked-files=alland transient-directory audit✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.