Skip to content

fix(bin): prefer a live no-mistakes run over a terminal one - #2881

Merged
kunchenguid merged 3 commits into
kunchenguid:mainfrom
jayjongcheolpark:fm/crewstate-live-run-preference
Sep 9, 2026
Merged

fix(bin): prefer a live no-mistakes run over a terminal one#2881
kunchenguid merged 3 commits into
kunchenguid:mainfrom
jayjongcheolpark:fm/crewstate-live-run-preference

Conversation

@jayjongcheolpark

@jayjongcheolpark jayjongcheolpark commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Intent

The captain reports that PR #2881 ("fix(bin): prefer a live no-mistakes run over a terminal one", branch fm/crewstate-live-run-preference, head 54a412b, CI green 14/14) now has MERGE CONFLICTS with main. Captain's words: "2881 은 머지 컨플릭이 있네요" - rebase it onto current origin/main so it can be merged.

What 2881 delivers (all of it must survive the rebase): when a task's worktree binds to more than one recorded no-mistakes run, bin/fm-crew-state.sh (via the branch-and-code-identity matching rule in bin/fm-nm-run-lib.sh) must prefer a LIVE (non-terminal) run over a terminal one; among candidates of the same liveness class the existing precedence is unchanged; the policy is stated once in the matching rule's documented contract; 4 regression tests in tests/fm-crew-state.test.sh reproduce the proven case (terminal run at the worktree's exact commit plus a live run descending from it); no behavior change for single-run or terminal-only cases; docs in docs/architecture.md and docs/configuration.md describe the rule.

Conflicting files against origin/main as of dispatch: bin/fm-crew-state.sh, bin/fm-nm-run-lib.sh, docs/architecture.md, docs/configuration.md, tests/fm-crew-state.test.sh.

What Changed

  • bin/fm-nm-run-lib.sh: fm_nm_runs_status_for_worktree no longer returns on the newest binding row when that row is terminal. It holds the terminal word, keeps scanning older rows for a running row that also binds to the worktree (by the head rule, or, when the live row's head is unfetched, only when the held terminal row sits at the worktree's exact HEAD), and prints the live word if found. Malformed rows now break and print the already-decided answer instead of swallowing it. A new fm_nm_run_status_class helper classifies ledger status words as terminal, live, or unknown, and the header comment states the live-over-terminal contract once.
  • bin/fm-crew-state.sh: when bare axi status binds this branch's run but the run is not active, the reader consults the runs ledger via the same matching rule and switches to the coarse live status only if a live sibling exists. Single-run, terminal-only, and unclassifiable cases keep their existing result and precedence.
  • tests/fm-crew-state.test.sh adds six regression tests for the crashed-run-at-exact-commit case, the runs-list path, the unfetched live sibling, and the no-widening guarantees. docs/architecture.md and docs/configuration.md describe the rule and the second ledger read behind FM_CREW_STATE_RUNS_LIMIT.

Risk Assessment

✅ Low: The fix-round commit is a small, bounded extension of the ledger scan that matches the user-authorized remedy exactly, its regression test executes the real crew-state script and fails without the change, teardown's expected-head caller is unaffected because its anchor check happens before the widened branch is reachable, and all intent-required deliverables (rule, single contract statement, tests, docs) are present after the rebase.

Testing

Ran the targeted fm-crew-state and fm-teardown test files on the target commit (all pass), then proved the regression by running the six new tests and a manual crew-state transcript against both the base and target bin scripts: the healthy task reads "state: failed" before the fix and "state: working" after, in both the proven descendant-head shape and the authorized unfetched-head shape, while terminal-only and unclassifiable-row cases are unchanged. No visual artifact applies because the surface is a CLI status line.

Evidence: Before fix (base 78318e2): crew-state transcript and new tests

Source: Before fix (base 78318e2): crew-state transcript and new tests

$ no-mistakes runs --limit 200 failed fm/crewstate-live-run-preference fe8498d 2026-08-05 11:20 running fm/crewstate-live-run-preference c09d485 2026-08-05 10:05 $ bin/fm-crew-state.sh demo state: failed · source: run-step · run failed (unfetched live head 0123abc: same) state: failed · source: run-step · run failed not ok - the live run outranks the terminal corpse bound to the same worktree not ok - an older live row outranks the branch's newest terminal row not ok - an unfetched live row anchored by the exact-head terminal row outranks it ok - two terminal rows keep the existing newest-first precedence ok - an unclassifiable status row keeps the ledger's newest-first precedence ok - a terminal run with no live sibling is unchanged new-test failures: 3

=== proven 2026-08 case (descendant live head) ===
worktree HEAD: fe8498d
$ no-mistakes axi status      -> run.status=completed outcome=failed head=<worktree HEAD>
$ no-mistakes runs --limit 200
  failed     fm/crewstate-live-run-preference fe8498d  2026-08-05 11:20
  running    fm/crewstate-live-run-preference c09d485  2026-08-05 10:05
$ bin/fm-crew-state.sh demo
state: failed · source: run-step · run failed

=== routine unfetched-head case (unfetched live head) ===
worktree HEAD: fe8498d
$ no-mistakes axi status      -> run.status=completed outcome=failed head=<worktree HEAD>
$ no-mistakes runs --limit 200
  failed     fm/crewstate-live-run-preference fe8498d  2026-08-05 11:20
  running    fm/crewstate-live-run-preference 0123abc  2026-08-05 10:05
$ bin/fm-crew-state.sh demo
state: failed · source: run-step · run failed

=== six new regression tests against /tmp/fm-livepref.JXlA/base/bin/fm-crew-state.sh ===
not ok - the live run outranks the terminal corpse bound to the same worktree (missing: 'state: working')
--- output ---
state: failed · source: run-step · run failed
FAIL: test_terminal_corpse_loses_to_live_run_on_same_branch
not ok - an older live row outranks the branch's newest terminal row (missing: 'state: working')
--- output ---
state: failed · source: run-step · run failed
FAIL: test_runs_list_live_row_outranks_newer_terminal_row
not ok - an unfetched live row anchored by the exact-head terminal row outranks it (missing: 'state: working')
--- output ---
state: failed · source: run-step · run failed
FAIL: test_unfetched_live_sibling_outranks_terminal_row_at_exact_head
ok - two terminal rows keep the existing newest-first precedence
ok - an unclassifiable status row keeps the ledger's newest-first precedence
ok - a terminal run with no live sibling is unchanged
new-test failures: 3
Evidence: After fix (target 185a780): crew-state transcript and new tests

Source: After fix (target 185a780): crew-state transcript and new tests

$ no-mistakes runs --limit 200 failed fm/crewstate-live-run-preference 9b9cd47 2026-08-05 11:20 running fm/crewstate-live-run-preference 6646b6e 2026-08-05 10:05 $ bin/fm-crew-state.sh demo state: working · source: run-step · validating (background run) (unfetched live head 0123abc: same) state: working · source: run-step · validating (background run) ok - a live run outranks a terminal run bound to the same worktree ok - runs-list selection prefers a live row over a newer terminal one ok - an unfetched live sibling outranks a terminal row at the worktree's exact commit ok - two terminal rows keep the existing newest-first precedence ok - an unclassifiable status row keeps the ledger's newest-first precedence ok - a terminal run with no live sibling is unchanged new-test failures: 0

=== proven 2026-08 case (descendant live head) ===
worktree HEAD: 9b9cd47
$ no-mistakes axi status      -> run.status=completed outcome=failed head=<worktree HEAD>
$ no-mistakes runs --limit 200
  failed     fm/crewstate-live-run-preference 9b9cd47  2026-08-05 11:20
  running    fm/crewstate-live-run-preference 6646b6e  2026-08-05 10:05
$ bin/fm-crew-state.sh demo
state: working · source: run-step · validating (background run)

=== routine unfetched-head case (unfetched live head) ===
worktree HEAD: 9b9cd47
$ no-mistakes axi status      -> run.status=completed outcome=failed head=<worktree HEAD>
$ no-mistakes runs --limit 200
  failed     fm/crewstate-live-run-preference 9b9cd47  2026-08-05 11:20
  running    fm/crewstate-live-run-preference 0123abc  2026-08-05 10:05
$ bin/fm-crew-state.sh demo
state: working · source: run-step · validating (background run)

=== six new regression tests against ~/.no-mistakes/worktrees/22794bb0df3c/01M23VZT31J6WAHMQWBZFZ2ZW2/bin/fm-crew-state.sh ===
ok - a live run outranks a terminal run bound to the same worktree
ok - runs-list selection prefers a live row over a newer terminal one
ok - an unfetched live sibling outranks a terminal row at the worktree's exact commit
ok - two terminal rows keep the existing newest-first precedence
ok - an unclassifiable status row keeps the ledger's newest-first precedence
ok - a terminal run with no live sibling is unchanged
new-test failures: 0
Evidence: Full tests/fm-crew-state.test.sh log on target

Source: Full tests/fm-crew-state.test.sh log on target

ok - active run-step is authoritative
ok - stale needs-decision over active run is superseded
ok - stale blocked over active run is superseded
ok - daemon/timeout blocked claim over a live fixing run reads as run alive
ok - socket refusal or missing socket over a stale fixing run reports blocked
ok - socket refusal over a terminal attributed run reports blocked
ok - broken-pipe blocker over a live run keeps the plain superseded reading
ok - genuine daemon-down blocked line still reports blocked
ok - genuine parked run is not flagged superseded
ok - scalar gate parked run is not flagged superseded
ok - gate block parked run is not flagged superseded
ok - ci-ready status log beats monitoring run
ok - ci-monitoring run with checks already green surfaces done
ok - top-level ci status uses ci log green marker
ok - terminal no-checks ci-monitor marker surfaces done
ok - base-advance rearm after green stays working
ok - pending no-checks ci-monitor marker stays working
ok - ci-monitoring run with checks not yet green stays working
ok - a fresh issue after an earlier green reading is not masked
ok - stale checks-green status log does not mask CI relapse
ok - ci fixing is not overridden by an earlier green marker
ok - top-level fixing is not overridden by a stale ci running row
ok - top-level fixing is not overridden by a stale done log
ok - terminal passed run is authoritative
ok - terminal failed run is authoritative
ok - orphaned ci monitor after green reads as held-for-merge done
ok - status-only failed orphaned ci monitor after green reads done
ok - genuinely failing CI keeps the failed verdict
ok - a second failed step disqualifies the orphaned-monitor reclassification
ok - cross-branch run is attributed via the real runs list
ok - socket refusal over a coarse active run reports blocked
ok - failed ledger record reads unknown only when the daemon is provably down
ok - cross-branch attribution picks the branch's most recent row
ok - a live run outranks a terminal run bound to the same worktree
ok - runs-list selection prefers a live row over a newer terminal one
ok - an unfetched live sibling outranks a terminal row at the worktree's exact commit
ok - two terminal rows keep the existing newest-first precedence
ok - an unclassifiable status row keeps the ledger's newest-first precedence
ok - a terminal run with no live sibling is unchanged
ok - coarse run does not probe another branch's ci log
ok - another branch's run is ignored, falls back
ok - no run + a busy semantic record reads working, attributed to its source
ok - a converted adapter never reads working from rendered footer text
ok - grok still reads working through its isolated rendered-tail fallback
ok - herdr's native busy verdict reads working with no record present
ok - a herdr CLI that fails to answer reads unknown/unreachable, never gone
ok - an alive endpoint whose scrollback read failed stays working
ok - a husk pane (agent gone) still reads gone for reclaim
ok - a mid-tool-call crew stays working because its record outranks herdr's generation state
ok - an idle record with idle agent_status stays not-busy (no regression for a human-blocked agent)
ok - no run + idle pane uses the status-log verb
ok - no run + idle pane parses keyed status syntax
ok - no run + idle pane on a paused: status reports state: paused with its reason
ok - no run + idle pane honors the configured paused verb
ok - a trailing resolved: event does not corrupt state render (idle stays idle)
ok - dead window ignores stale status log
ok - a tmux that fails to answer reads unknown/unreachable, never gone
ok - closed pane still reports a terminal run-step
ok - closed pane still reports an active run-step
ok - no timeout command uses perl bound
ok - scout skips the run lookup
ok - torn-down worktree is handled gracefully
ok - fm-crew-state remote: alive endpoint falls through to the routed status log
ok - fm-crew-state remote: an idle alive endpoint reads alive, never gone or dead
ok - fm-crew-state remote: an unreachable host reads unknown-remote, never gone or dead
ok - fm-crew-state remote: the remote host's own dead verdict is reported truthfully
ok - missing meta is handled gracefully
ok - crew_is_provably_working absorbs a validating crew found only via the runs-list fallback
ok - crew_is_provably_working still surfaces a genuinely stopped crew (safety property preserved)
ok - usage error exits 2
ok - historical same-branch rewritten head is not attributed as current
ok - active run with valid descendant fix head remains current
ok - local work advanced past run head invalidates attribution
ok - pipeline-owned active run binds without head equality and beats the failed row
ok - a genuinely failed run with no later run is not hidden
ok - coarse scan anchors the unresolvable active row instead of falling to an older one
ok - coarse scan with a mismatched anchor stays unknown and lets the pane answer
ok - the exemption requires branch_sync.state=pipeline_owned
ok - the exemption never applies to a terminal run
ok - missing run head falls back instead of matching by branch
ok - active fix round with an unfetched pipeline head reads working
ok - unanchored unverifiable active row is never attributed
ok - unresolvable terminal row never reads as current
ok - runs-list continuation attribution works when axi answers another branch
all fm-crew-state tests passed
Evidence: Full tests/fm-teardown.test.sh log on target

Source: Full tests/fm-teardown.test.sh log on target

ok - local-only worktree with HEAD on a fork remote is torn down and the home summary is refreshed
ok - teardown closes its own backlog item before reporting success
ok - teardown honors config/backlog-backend=manual and still finishes cleanly
ok - local-only worktree with truly unpushed work is refused (safety preserved)
ok - local-only worktree with work merged into local main is torn down (no regression)
ok - no-mistakes worktree with HEAD on origin is torn down (no regression)
ok - no-mistakes worktree with genuinely unlanded work is refused (safety preserved)
ok - local-only worktree with unpushed work is torn down under --force (escape hatch)
ok - fm-pr-check publishes the PR-ready line on a secondmate's parent channel once
ok - a secondmate home's teardown delivers the child's final line or refuses until it can
ok - teardown completes when an exact busy-state sidecar is already absent
ok - herdr teardown removes pane-owned escalation dedupe state
ok - herdr flat teardown refuses before returning the isolated copy under lock contention and the retry completes cleanly
ok - herdr flat teardown never erases records when pane presence is unparseable
ok - herdr flat teardown preflight refuses before every destructive change
ok - forced secondmate teardown preflights every Herdr child before cleanup mutation
ok - forced secondmate teardown holds every descendant lifecycle and metadata lock
ok - forced secondmate teardown retains Herdr child identity until exact pane disappearance
ok - forced teardown retains a nested secondmate home and its grandchild's Herdr identity when the grandchild close is unconfirmed
ok - herdr projection teardown retires its journal only after confirming the exact recorded pane is gone
ok - herdr projection teardown retains every record when post-close presence is unknown
ok - herdr projection teardown surfaces failed focus restoration without turning confirmed cleanup into a hard failure
ok - squash-merged + deleted-branch worktree (PR merged) is torn down (the fix)
ok - squash-merged PR accepts a local HEAD that is an ancestor of the final PR head
ok - teardown discovers a merged PR by branch name and tears down when no pr= was ever recorded
ok - squash-merged PR accepts replayed unpushed local patches contained in the PR head
ok - merged PR does not allow teardown after a later local commit
ok - squash-merged task whose local branch followed the pipeline rebase is torn down
ok - squash-merged same-path different content still refuses
ok - squash-merged rebased local still refuses a genuinely unlanded follow-up commit
ok - squash-merged stale local still refuses when the forge is unreachable
ok - fm-pr-check does not refresh PR head after HEAD moves
ok - fm-pr-check records the remote PR head when the local worktree lags
ok - worktree whose content already landed in the default branch is torn down (content fallback)
ok - content fallback refreshes origin default before comparing trees
ok - dirty worktree is refused even when its committed work has landed (dirty always wins)
ok - gh lookup error with content not in default refuses (fail-safe)
ok - a record predating spawn_gen refuses teardown until --legacy-record is passed
ok - a landed legacy record with a dead endpoint tears down and logs its accepted incarnation
ok - --legacy-record never relaxes the unlanded-work refusal
ok - an endpoint that cannot be confidently read as dead refuses --legacy-record teardown
ok - --legacy-record teardown rolls its stamp back when the close marker write fails
ok - a legacy stamp a failed rollback left behind still faces the endpoint gate
ok - a corrupt spawn_gen is never accepted as a legacy record
ok - provably-stale worktree index.lock (old, no live holder) is cleared and teardown succeeds
ok - live-held worktree index.lock is never removed and teardown refuses
ok - lsof errors leave worktree index.lock in place and refuse teardown
ok - stale lock cleanup rechecks and refuses dirty worktree before return
ok - normal repo index.lock is resolved from the worktree and cleared when stale
ok - index-lock mtime fault injection is PATH-based; skipped on Darwin where stat is /usr/bin/stat
ok - transient index.lock cleared after first failed return is retried successfully without force-remove
ok - persistent index.lock exhausts retries and refuses without force-removing the lock
ok - empty retry wait overrides use the default without aborting teardown
ok - fractional legacy retry wait remains supported without arithmetic
ok - a task's own parked no-mistakes run is aborted, not orphaned, before the worker is removed
ok - a parked run the pipeline advanced past the task copy is still concluded from the runs ledger, not orphaned
ok - a ledger row for a different head never authorizes a parked-run abort
ok - a malformed ledger row never authorizes a parked-run abort
ok - an impossible ledger date never authorizes a parked-run abort
ok - a terminal status with a stale gate never reaches ledger cleanup
ok - an advanced head present locally aborts through the strict rule alone - the ledger fallback stays dormant
ok - an unresolvable active row with no same-branch anchor is never concluded (conservative refusal)
ok - an ancestor-only anchor never binds an advanced parked run to this task
ok - a terminal unfetched-head row is stale history and never concludes a run
ok - a terminal newest row anchored at this worktree's head never authorizes an abort
ok - a resolvable diverged newer same-branch row makes every older row stale history; no run is concluded
ok - consecutive unresolvable rows are ambiguous and never conclude a run
ok - a ledger-proven continuation is still left alone while the run is autonomously active
ok - teardown refuses before reap or removal when a task-owned run remains parked
ok - a different run cannot confirm the targeted abort
ok - empty post-abort status is not accepted as confirmation
ok - the CLI's exact run-not-found signal confirms completion
ok - a parked run on another branch is never aborted by this task's teardown (ownership is precise)
ok - a task-owned autonomous running step is left alone rather than aborted
ok - a leaked descendant process rooted under the task's worktree is reaped by teardown, not left surviving
ok - a leaked descendant process rooted under the task's per-task tasktmp is reaped by teardown too
ok - missing lsof falls back to reaping the tmux pane process group
ok - an erroring lsof scan refuses teardown and preserves the task
ok - a reused pid with a different start time is never force-killed
ok - an exec change preserves birth identity and the process is reaped
ok - a process spawned during grace is reaped on a later pass
ok - persistent leaked processes refuse teardown after bounded retries
ok - a process exiting during identity lookup does not block teardown
ok - the run abort and the leaked-process reap both complete before the destructive worktree return

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ bin/fm-nm-run-lib.sh:241 - The live-over-terminal displacement only fires when the older live row's sha resolves in the task worktree (fm_nm_head_matches_worktree at line 241). The reproduced case commits the descendant locally, so tests pass, but the lib's own header (fm_nm_run_is_pipeline_owned_active) states that a live run's lane head is routinely NOT a git object in the task copy because fix-round commits live only in the gate repo. Concrete path: axi status returns the failed corpse at the worktree's exact commit; ledger newest row is that corpse, older row is running &lt;branch&gt; &lt;unfetched-sha&gt;; the scan sets decided=failed, reaches the running row, head match fails, continue, prints failed; crew-state then reports the healthy task as failed. The anchored pending_st path does not help because it only recognizes an unresolvable running row when it is the NEWEST branch row. The intent authorizes only the proven descendant-present case, so this is not a conformance contradiction, but the same authorized failure is reachable in the routine unfetched-head shape. The remedy (accept an unresolvable running row as the live sibling when the held terminal row sat at the worktree's exact commit, mirroring the anchor rule) widens the ledger rule's semantics, so it needs authorization rather than an auto-fix.
  • ℹ️ bin/fm-crew-state.sh:590 - Every crew whose bound axi status run is terminal (the steady state of a finished task) now issues one additional no-mistakes runs --limit 200 call per state read to look for a live sibling. It is bounded by FM_CREW_STATE_NM_TIMEOUT and only runs for ships with a branch and a bound terminal run, so this is an accepted cost of the fix, noted for awareness.
  • ℹ️ tests/fm-crew-state.test.sh:1132 - The intent lists 4 regression tests; the branch carries 5 (the extra one, test_terminal_run_without_live_sibling_is_unchanged, covers the required 'no behavior change for terminal-only cases' criterion). All five execute the real fm-crew-state.sh against fake CLI output and assert emitted state, so none is a source-content-only assertion. Over-delivery, not a contradiction.

🔧 Fix: accept unfetched live sibling anchored at exact worktree head
1 info still open:

  • ℹ️ bin/fm-nm-run-lib.sh:249 - The authorized unfetched-head rule is implemented as specified: an unresolvable running row on the same branch displaces a held terminal row only when that terminal row resolved to exactly the worktree HEAD (decided_exact at line 279), a resolvable live row still has to bind via fm_nm_head_matches_worktree (line 247), and every other exit path is unchanged (traced: terminal-only, unknown newest word, non-binding newest row, pending_st anchor, malformed rows now break and print the already-decided word or nothing). One property worth knowing about, inherent in the authorized remedy rather than a defect in its implementation: unlike the pipeline-continuation anchor, which admits only the IMMEDIATELY older row, this rule accepts an unresolvable running row at any depth below the terminal anchor (intervening same-branch terminal rows are skipped at line 245). Since the ledger is ordered by last touch, such a row was touched less recently than the crashed corpse, so a running row that a crashed daemon never finalized would keep displacing a later, legitimately failed run at the same exact commit for as long as it stays on the ledger. The user explicitly accepted this widening, so no action is requested.
✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-crew-state.test.sh on target 185a780 (all 85 cases pass, including the 6 new live-over-terminal cases)
  • bash tests/fm-teardown.test.sh on target 185a780 (all 84 cases pass; fm-teardown.sh is the lib's other consumer of fm_nm_runs_status_for_worktree)
  • Regression proof: extracted base 78318e2 via git archive, ran the 6 new tests against the base bin scripts: test_terminal_corpse_loses_to_live_run_on_same_branch, test_runs_list_live_row_outranks_newer_terminal_row, test_unfetched_live_sibling_outranks_terminal_row_at_exact_head fail on base and pass on target; test_only_terminal_rows_keep_newest_first_precedence, test_unknown_status_row_keeps_newest_first_precedence, test_terminal_run_without_live_sibling_is_unchanged pass on both
  • Manual end-user transcript: invoked bin/fm-crew-state.sh &lt;id&gt; over a real throwaway git worktree with fake no-mistakes axi status (failed run at worktree HEAD) and fake no-mistakes runs --limit 200 (failed newest row + older running row) for both the descendant-head and unfetched-head shapes, on base and target
  • git merge-base --is-ancestor origin/main 185a780 confirms the branch is rebased onto current origin/main
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 warning
  • ⚠️ linter found issues (exit code 1)
✅ **Push** - passed

✅ No issues found.

@greptile-apps

greptile-apps Bot commented Aug 23, 2026

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge.

Comment thread bin/fm-crew-state.sh Outdated
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate:

First look on HEAD e9306d05437515b3031bd78b5dbef74fb3cdab25 vs main 55d40691ac30a4664217b4e20208287baa88eb21. MERGEABLE/UNSTABLE. jayjongcheolpark is not a blocked author. Author force-pushed this tip 2026-09-09T17:07:34Z (committer date on e9306d…), so last author activity is todaynot 14-day stale; Greptile's 2026-08-23 comment does not reset the clock, but the Sep 9 author push does. No close under the 14-day skill.

Contract-class: restore. Selection defect in shared no-mistakes run matching: when multiple recorded runs bind to the same worktree/branch identity, the unconfigured path could prefer a terminal corpse over a live run, misreporting a healthy task as failed. Tip states live-over-terminal preference in fm_nm_head_matches_worktree's contract (bin/fm-nm-run-lib.sh), adds fm_nm_run_status_class, and applies it on both fm-crew-state.sh selection paths (runs listing + provisional terminal axi status). That restores the product promise that crew state reflects live no-mistakes work, not a dead sibling run. Not new-default / not opt-in.

VISION.md (each rule)

  • One captain, one interface: aligns — honest crew-state labels for the captain.
  • Authority is explicit and never inferred: aligns — corrective selection only; no new consent-assuming default capability.
  • Scripts own the mechanics, agents own the judgment: aligns — deterministic classifier + selection in scripts.
  • A restart is a non-event: aligns — read-side over recorded runs; no custody change.
  • Delegation with a spine: aligns — safer supervision signal (live vs terminal).
  • The fleet outlives any vendor: aligns — contracts to no-mistakes status words, not a UI.
  • Scope: aligns — command-layer crew-state read path; validation remains no-mistakes.

Attestation: MISMATCH — body marker still 325da1623e44ba4f0e7914b24b39285213ad2f31 ≠ tip e9306d054375…. Require no-mistakes 34380995059 FAILURE (expected until rebind). CI 34380995112 SUCCESS (lint/coverage/portable/Herdr/timing/macOS/invariants). Greptile Review SUCCESS / 5/5 on tip (open note is an unreachable unknown-status P2, not a tip blocker).

Waiting on the author: raise/refresh via git push no-mistakes so Pipeline attestation MATCHes tip e9306d… and Require no-mistakes goes green. Then re-triage. Merge-eligible N. Do not merge. Firstmate-flag no (not otherwise-ready).

jayjongcheolpark and others added 3 commits September 9, 2026 15:58
A worktree can bind to more than one recorded no-mistakes run at once.
The branch-and-code-identity rule in bin/fm-nm-run-lib.sh accepts both an
exact-equal commit and a worktree-is-an-ancestor match, but never stated
which wins when both bind, so the tie fell to whichever candidate the
caller reached first.

Observed on a live fleet: a crashed validation daemon left a FAILED run
at the worktree's own commit while the live run that replaced it
validated a descendant commit on the same branch. Bare `axi status`
answers with the most-recently-touched run - the corpse - and it bound by
the equal-commit rule, so every recomputation reported `failed` for a
task whose real run was healthy. The same label had also read `failed`
earlier while the work was genuinely stalled, so the signal was wrong in
both directions.

State the live-over-terminal policy in the matching rule's own contract,
where the equal-commit and ancestor rules already live, and add
fm_nm_run_status_class as the one classifier that decides liveness from a
recorded status word. fm-crew-state.sh applies it on both selection
paths: the runs listing now scans past a terminal row for a live one, and
a terminal `axi status` answer is provisional until the listing has been
asked whether this worktree also has a live run.

Same-liveness-class candidates keep the listing's newest-first
precedence, and a status word the classifier cannot place keeps the
caller's own ordering rather than displacing a known result, so a
single-run task and a task whose runs are all terminal are unchanged.

Regression coverage reproduces the proven case (terminal run at the
worktree's exact commit plus a live run descending from it) and its
runs-list twin; both fail under the old tie-break. Two companion cases
pin the no-widening half - two terminal rows still resolve newest-first,
and a terminal run with no live sibling keeps its full run-step detail -
and both pass before and after the change.
The FM_CREW_STATE_RUNS_LIMIT comment in bin/fm-crew-state.sh still described
the runs ledger as scanned only by the cross-branch fallback, but the
live-over-terminal fix also consults it as the live-sibling probe behind a
terminal axi status answer. Point the comment at docs/configuration.md as the
setting's owner instead of restating a second copy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jayjongcheolpark
jayjongcheolpark force-pushed the fm/crewstate-live-run-preference branch from e9306d0 to eee895f Compare September 9, 2026 20:23
@kunchenguid
kunchenguid merged commit 28153d1 into kunchenguid:main Sep 9, 2026
16 of 17 checks passed
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: this is merged. Thank you @jayjongcheolpark — really appreciate you taking the time on this.

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.

2 participants