Skip to content

fix(bin): claim a merged PR only when the pr step and every step after it completed - #3527

Open
loom-loki wants to merge 4 commits into
kunchenguid:mainfrom
loom-loki:fm/fm-upstream-crew-state-merge-claim
Open

fix(bin): claim a merged PR only when the pr step and every step after it completed#3527
loom-loki wants to merge 4 commits into
kunchenguid:mainfrom
loom-loki:fm/fm-upstream-crew-state-merge-claim

Conversation

@loom-loki

@loom-loki loom-loki commented Sep 2, 2026

Copy link
Copy Markdown

Intent

Contribute an already-written, already-proven fix upstream to this project. Nothing to design, nothing new to implement.

THE BUG: bin/fm-crew-state.sh claimed a no-mistakes run had merged a pull request when it had not. A run reaches outcome=passed once its steps finish without failing, which INCLUDES a run whose pr and ci steps were SKIPPED. Such a run opened and merged nothing, yet the reader mapped outcome=passed to the fixed detail "run passed: PR merged/closed", asserting a merge it had never observed. Verified on no-mistakes v1.60.2: run 01M1EA5NJVP18AE7SPY5MBYW42 reported pr,skipped and ci,skipped under outcome=passed while the forge still had that branch PR open and unmerged, and the work was reported as landed.

THE FIX (deliberate design decision): state the rule GENERALLY rather than special-casing one step. The merge claim is made only when the run own pr step AND every step it reports after that one actually completed. This is deliberate: the same false-merge claim existed one trigger over, when ci rather than pr was skipped, and both are closed by the one general rule. Do NOT reintroduce a per-step special case, and do NOT hardcode any step name after pr - the steps a merge claim depends on are read from the run own step order, because axi status carries no merge signal to key on directly. A run that passed with the pull request opened but checks unverified now reports that honestly instead of claiming a merge.

Implementation: generalises the previous ci-only nm_ci_step_status into nm_step_rows/nm_step_status with a NUMERIC-third-column guard, and adds nm_passed_pr_detail. The numeric guard is load-bearing and deliberate: the findings table is emitted BEFORE the steps table, so without it a finding whose id is pr is matched first and shadows the genuine pr step row, reporting the finding SEVERITY as the step status. nm_ci_step_status is deliberately kept as a narrow wrapper that returns only running/fixing so its caller falls through unchanged.

ACCEPTED REFINEMENTS ALREADY APPLIED IN THIS BRANCH (do not undo, do not re-litigate):

  1. All five details nm_passed_pr_detail can emit end in the same suffix "merge state unknown to the run"; the skipped-pr branch deliberately drops a redundant "by the run" rather than repeating it. Every corresponding test asserts that FULL suffix, not just the prefix, so a future edit to any one detail fails the suite rather than drifting silently.
  2. The active-run side of the numeric guard is pinned by a fixture BUILT FROM OBSERVED OUTPUT, not from assumption: axi status was captured mid-step on live runs of no-mistakes v1.60.2 (eb4e379, built 2026-08-29), sampled on three different running steps - three distinct step names, not three runs - and every sample renders the running step as <step>,running,0,0 in the steps table beside <step>,running,<duration>,... in a separate active_steps table whose third column is a duration. The fixture and the comment both record that this shape was observed on that version rather than inferred, because a vendor output shape is a fact about a version, not a law. Mutation-tested: break the reader so it stops matching the active run steps row and that test fails with the exact incident signature, a green PR reading as still validating.

TESTS: tests/fm-crew-state.test.sh covers the skipped pr step, a completed pr step with a skipped ci step, pr as the last reported step, a run with no pr row at all, the still-valid genuinely-merged case, the active-run both-tables case above, and a NEGATIVE case for the findings-row guard. That negative case is deliberate and has been verified to fail when the guard is removed - an earlier attempt at it would have passed with the guard gone, which is exactly what it must not do.

SCOPE CONSTRAINT (explicit, deliberate): exactly two files - bin/fm-crew-state.sh and tests/fm-crew-state.test.sh. Touching a third file is out of scope. This fix carries no dependency on any forge-specific or provider-specific support code and must not acquire one.

DELIVERY: this is a contribution to somebody else project. The pull request body must stand on its own for a maintainer with none of this context: what the bug is, how it reproduces, why the fix is stated generally rather than per-step, and what the tests prove. Do not merge. Stop when the pull request is open and its checks are green.

NOTE ON THIS RUN: a previous run of this same branch reached the push step and failed there on a transient SSH authentication error from the forge; the identical command has since succeeded repeatedly. The two review-fix commits from that run are preserved on this branch and custody was returned through the supported recovery path. Nothing else changed.

What Changed

  • Bug: bin/fm-crew-state.sh mapped a terminal outcome=passed run to the fixed detail run passed: PR merged/closed, asserting a merge it had never observed. A run reaches outcome=passed once its steps finish without failing, which includes a run whose pr and ci steps were skipped - the shape no-mistakes takes when it cannot resolve a push provider, so nothing was opened or merged. Reproduced on no-mistakes v1.60.2: run 01M1EA5NJVP18AE7SPY5MBYW42 reported pr,skipped,0,17 and ci,skipped,0,16 under outcome=passed while the forge still had that branch's pull request open and unmerged, and the work was reported as landed.
  • Fix: new nm_passed_pr_detail claims a merge only when the run's own pr step and every step the run reports after it read completed; otherwise the detail names the step that stopped short and ends in merge state unknown to the run. The rule is stated generally rather than special-cased on one step because the same false claim existed one trigger over (axi run --skip=ci leaves pr completed, ci skipped, PR open), and no step name after pr is hardcoded - the steps a merge claim depends on are read from the run's own step order, since axi status carries no merge signal to key on directly. Supporting change: the ci-only nm_ci_step_status is generalised into nm_step_rows/nm_step_status with a numeric-third-column guard, needed because the findings table is emitted before the steps table and a finding whose id is pr would otherwise shadow the real step row and report the finding's severity as the step status; nm_ci_step_status remains as a narrow wrapper returning only running/fixing so its caller falls through unchanged.
  • Tests: seven cases added to tests/fm-crew-state.test.sh covering a skipped pr step, a completed pr step with a skipped ci step, pr as the last reported step, a run with no pr row at all, the still-valid genuinely-merged run (fixture from real run 01M1669Y82JTHWEBSG7PR2TKNH), an active run rendering both the steps and active_steps tables (fixture built from axi status captured mid-step on live v1.60.2 runs, pinning that the numeric guard did not break the green-checks override behind the PR fix(backends): scope zellij tabs by firstmate home #252 incident), and a negative case that fails if the findings-row guard is removed.

Risk Assessment

✅ Low: A well-bounded two-file change to one read-only reporting helper that replaces an over-claiming fixed string with a conservative, generally-stated derivation, backed by fixtures copied from real observed output and a negative test for the load-bearing guard; every traced input produces a correct detail and no downstream consumer parses the changed string.

Testing

I reproduced the reported bug end-to-end before testing the fix: running bin/fm-crew-state.sh from the base commit against the real v1.60.2 axi status output for run 01M1EA5NJVP18AE7SPY5MBYW42 (pr and ci skipped, outcome passed) prints run passed: PR merged/closed, and this branch prints run passed, PR step skipped: no PR was opened or merged, merge state unknown to the run; the same before/after holds for the --skip=ci shape, while the genuinely landed run still reads as merged on both. I followed the detail through to the surfaces a captain and firstmate actually see - the /bearings Underway row and the fleet-snapshot JSON - and confirmed the longest new detail (87 chars) survives the board's 90-char truncation with its suffix intact. Each of the five new merge-claim tests fails individually against the pre-fix reader with the exact false-merge signature and passes here, and the two invariant tests that necessarily pass both before and after are pinned by mutation instead: widening the numeric-third-column guard makes the findings-row test fail with a finding severity reported as the pr step status, and narrowing it so an active run's own steps row stops matching makes the active-run test fail with a green PR reading as still validating. The targeted suite (65 cases) and the downstream fleet-snapshot view suite are green, no other suite asserts on this detail string, and the worktree is clean at d813ec0 with all mutations reverted. This is a CLI text-line change with no rendered UI surface, so the evidence is CLI transcripts rather than screenshots.

Evidence: Before/after reader transcript over the real observed axi status shapes
fm-crew-state.sh - false 'PR merged/closed' claim: BEFORE (base 8988af2) vs AFTER (d813ec0)
Each scenario feeds the reader a real `no-mistakes axi status` shape observed on v1.60.2
and runs the SAME command against both readers. The one-line output is the reader's
product interface - the line firstmate reads every heartbeat.

===============================================================================
SCENARIO: run 01M1EA5NJVP18AE7SPY5MBYW42 shape - outcome=passed, pr and ci SKIPPED (no PR ever opened; the forge still had the branch PR open and unmerged)
-------------------------------------------------------------------------------
$ no-mistakes axi status            # what the reader is given
run:
  id: "01M1EA5NJVP18AE7SPY5MBYW42"
  branch: fm/feat-prskip
  status: completed
  head: "<worktree HEAD>"
  findings: "1 awaiting, 3 auto-fix, 42 info"
  steps[9]{step,status,findings,duration_ms}:
    intent,completed,0,13
    rebase,skipped,42,521
    review,completed,3,836695
    test,completed,0,704937
    document,completed,0,939357
    lint,completed,1,16
    push,completed,0,697
    pr,skipped,0,17
    ci,skipped,0,16
outcome: passed

$ bin/fm-crew-state.sh prskip     # BEFORE (base 8988af2)
state: done · source: run-step · run passed: PR merged/closed

$ bin/fm-crew-state.sh prskip     # AFTER  (this branch d813ec0)
state: done · source: run-step · run passed, PR step skipped: no PR was opened or merged, merge state unknown to the run

===============================================================================
SCENARIO: `no-mistakes axi run --skip=ci` shape - outcome=passed, pr COMPLETED (PR opened) but the later ci step SKIPPED, so the PR was left open
-------------------------------------------------------------------------------
$ no-mistakes axi status            # what the reader is given
run:
  id: "01RUNCISKIP"
  branch: fm/feat-ciskip
  status: completed
  head: "<worktree HEAD>"
  pr: "https://github.com/o/r/pull/1"
  findings: "1 auto-fix"
  steps[9]{step,status,findings,duration_ms}:
    intent,completed,0,2
    rebase,completed,0,1953
    review,completed,3,6046577
    test,completed,1,1385831
    document,completed,2,418814
    lint,completed,1,13
    push,completed,0,4179
    pr,completed,0,43885
    ci,skipped,0,16
outcome: passed

$ bin/fm-crew-state.sh ciskip     # BEFORE (base 8988af2)
state: done · source: run-step · run passed: PR merged/closed

$ bin/fm-crew-state.sh ciskip     # AFTER  (this branch d813ec0)
state: done · source: run-step · run passed, PR opened but ci step skipped: merge state unknown to the run

===============================================================================
SCENARIO: run 01M1669Y82JTHWEBSG7PR2TKNH shape - the genuinely landed run: pr completed AND every step after it completed
-------------------------------------------------------------------------------
$ no-mistakes axi status            # what the reader is given
run:
  id: "01M1669Y82JTHWEBSG7PR2TKNH"
  branch: fm/feat-merged
  status: completed
  head: "<worktree HEAD>"
  pr: "https://github.com/o/r/pull/1"
  findings: "3 awaiting, 3 auto-fix, 1 info"
  steps[9]{step,status,findings,duration_ms}:
    intent,completed,0,2
    rebase,completed,0,1953
    review,completed,3,6046577
    test,completed,1,1385831
    document,completed,2,418814
    lint,completed,1,13
    push,completed,0,4179
    pr,completed,0,43885
    ci,completed,0,31779488
outcome: passed

$ bin/fm-crew-state.sh merged     # BEFORE (base 8988af2)
state: done · source: run-step · run passed: PR merged/closed

$ bin/fm-crew-state.sh merged     # AFTER  (this branch d813ec0)
state: done · source: run-step · run passed: PR merged/closed

===============================================================================
SCENARIO: findings table emitted BEFORE the steps table, carrying a finding whose id is literally 'pr' - the numeric-third-column guard must not let it shadow the real pr step row
-------------------------------------------------------------------------------
$ no-mistakes axi status            # what the reader is given
run:
  id: "01RUNFIND"
  branch: fm/feat-findings
  status: completed
  head: "<worktree HEAD>"
  findings[1]{id,severity,file,line,action,description}:
    pr,warning,a.go,,auto-fix,ignored error
  steps[9]{step,status,findings,duration_ms}:
    intent,completed,0,13
    rebase,skipped,42,521
    review,completed,3,836695
    test,completed,0,704937
    document,completed,0,939357
    lint,completed,1,16
    push,completed,0,697
    pr,skipped,0,17
    ci,skipped,0,16
outcome: passed

$ bin/fm-crew-state.sh findings     # BEFORE (base 8988af2)
state: done · source: run-step · run passed: PR merged/closed

$ bin/fm-crew-state.sh findings     # AFTER  (this branch d813ec0)
state: done · source: run-step · run passed, PR step skipped: no PR was opened or merged, merge state unknown to the run

===============================================================================
SCENARIO: ACTIVE ci-monitoring run rendering BOTH tables (observed shape, v1.60.2):
          steps row 'ci,running,0,0' beside active_steps row 'ci,running,1m40s,...'
          CI is green; the reader must still find the ci step and fire the
          green-checks override, not read the duration-column active_steps row.
-------------------------------------------------------------------------------
run:
  id: "01RUNACTIVE"
  branch: fm/feat-active
  status: running
  head: "<worktree HEAD>"
  pr: "https://github.com/o/r/pull/2"
  findings: none
  steps[9]{step,status,findings,duration_ms}:
    intent,completed,0,12
    rebase,completed,0,77
    review,completed,0,22991
    test,completed,0,1385831
    document,completed,0,418814
    lint,completed,0,13
    push,completed,0,4179
    pr,completed,0,43885
    ci,running,0,0
  active_steps[1]{step,status,active_for,last_activity,agent_pid,round}:
    ci,running,1m40s,"12s ago: claude producing output","12345",starting

$ no-mistakes axi logs --step ci    # ci step log tail
all CI checks passed - still monitoring until merged or closed

$ bin/fm-crew-state.sh active     # AFTER  (this branch d813ec0)
state: done · source: run-step · checks green: PR ready for review (still monitoring for merge/close)
Evidence: Corrected detail on the captain-facing /bearings row and the fleet-snapshot JSON
The corrected detail as it reaches the two surfaces downstream of the reader:
the captain-facing /bearings Underway row (doing column, truncated at 90 chars -
the longest new detail is 87, so the 'merge state unknown to the run' suffix survives)
and the fleet-snapshot JSON firstmate consumes.

--- crew 'prskip' (pr+ci skipped: nothing was ever opened) ----------------------------------------------------------
$ bin/fm-bearings-snapshot.sh        # captain-facing /bearings Underway row
in_flight[1]{id,kind,state,repo,doing}:
  prskip,ship,done,"","run passed, PR step skipped: no PR was opened or merged, merge state unknown to the run"

$ bin/fm-fleet-snapshot.sh --json | jq '.tasks[].current_state'   # what firstmate consumes
{"state":"done","source":"run-step","detail":"run passed, PR step skipped: no PR was opened or merged, merge state unknown to the run"}

--- crew 'ciskip' (pr completed, ci skipped: PR opened, left open) ----------------------------------------------------------
$ bin/fm-bearings-snapshot.sh        # captain-facing /bearings Underway row
in_flight[1]{id,kind,state,repo,doing}:
  ciskip,ship,done,"","run passed, PR opened but ci step skipped: merge state unknown to the run"

$ bin/fm-fleet-snapshot.sh --json | jq '.tasks[].current_state'   # what firstmate consumes
{"state":"done","source":"run-step","detail":"run passed, PR opened but ci step skipped: merge state unknown to the run"}

--- crew 'merged' (genuinely landed: still reads as merged) ----------------------------------------------------------
$ bin/fm-bearings-snapshot.sh        # captain-facing /bearings Underway row
in_flight[1]{id,kind,state,repo,doing}:
  merged,ship,done,"","run passed: PR merged/closed"

$ bin/fm-fleet-snapshot.sh --json | jq '.tasks[].current_state'   # what firstmate consumes
{"state":"done","source":"run-step","detail":"run passed: PR merged/closed"}
Evidence: Regression tests failing pre-fix, plus both guard mutations failing their tests
=== 1. Do the new regression tests actually fail BEFORE the fix? ===============
bin/fm-crew-state.sh temporarily replaced with the base-commit (8988af2) version,
each new test then run on its own (the suite stops at its first failure).
test_terminal_passed_pr_skipped_claims_no_merge
    not ok - skipped pr step must not claim a merged PR (unexpected: 'PR merged')
    --- output ---
    state: done · source: run-step · run passed: PR merged/closed
test_terminal_passed_ci_skipped_claims_no_merge
    not ok - a PR the run never carried past opening must not read as merged (unexpected: 'merged/closed')
    --- output ---
    state: done · source: run-step · run passed: PR merged/closed
test_terminal_passed_with_pr_as_last_step_claims_no_merge
    not ok - a pr step with nothing after it must not claim a merged-or-closed PR (unexpected: 'merged/closed')
    --- output ---
    state: done · source: run-step · run passed: PR merged/closed
test_terminal_passed_without_a_pr_row_claims_no_merge
    not ok - an unreported pr step must not claim a merged-or-closed PR (unexpected: 'merged/closed')
    --- output ---
    state: done · source: run-step · run passed: PR merged/closed
test_findings_row_is_not_read_as_a_step_row
    not ok - the real skipped pr step must be read, not the pr-named finding (missing: 'PR step skipped')
    --- output ---
    state: done · source: run-step · run passed: PR merged/closed
test_terminal_passed_pr_completed_reads_as_landed
    ok - outcome=passed with a completed pr step still reports the merge
test_ci_monitoring_with_active_steps_table_still_reads_the_step_row
    ok - an active run's second active_steps table does not disturb the step-row reader

The last two pass before AND after: they are the no-regression pair (the genuinely
merged run must still read as merged; the active run must still be read at all).
They are pinned by mutation instead:

=== 2. Mutation A - drop the numeric-third-column guard =======================
nm_step_rows third column widened from [0-9]+ to [^,]+, so a findings row matches:
    | grep -E "^[[:space:]]*[A-Za-z0-9_-]+,[[:space:]]*\"?[A-Za-z0-9_-]+\"?[[:space:]]*,[[:space:]]*[^,]+[[:space:]]*,"

    not ok - the real skipped pr step must be read, not the pr-named finding (missing: 'PR step skipped')
    --- output ---
    state: done · source: run-step · run passed, PR step warning: merge state unknown to the run
and the line the captain would then see for a run that opened nothing
(the 'pr' FINDING's severity reported as the pr STEP's status):
    state: done · source: run-step · run passed, PR step warning: merge state unknown to the run

=== 3. Mutation B - stop matching an ACTIVE run's own steps row ===============
third column narrowed to [1-9][0-9]*, so the observed 'ci,running,0,0' no longer matches:
    | grep -E "^[[:space:]]*[A-Za-z0-9_-]+,[[:space:]]*\"?[A-Za-z0-9_-]+\"?[[:space:]]*,[[:space:]]*[1-9][0-9]*[[:space:]]*,"

    not ok - an active_steps table must not hide the ci step's own steps row (missing: 'state: done')
    --- output ---
    state: working · source: run-step · validating (running)
That failure IS the PR #252 incident signature - a green PR reading as still validating.

=== 4. Reader restored, same test green =======================================
    ok - an active run's second active_steps table does not disturb the step-row reader
Evidence: The bug and the fix, in one pair of lines
$ bin/fm-crew-state.sh prskip # BEFORE (base 8988af2)
state: done · source: run-step · run passed: PR merged/closed

$ bin/fm-crew-state.sh prskip # AFTER (this branch d813ec0)
state: done · source: run-step · run passed, PR step skipped: no PR was opened or merged, merge state unknown to the run

(input: the real `no-mistakes axi status` output for run 01M1EA5NJVP18AE7SPY5MBYW42 -
`pr,skipped,0,17` and `ci,skipped,0,16` under `outcome: passed`, while the forge still
had that branch's pull request open and unmerged)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-crew-state.test.sh - full targeted suite, 65 cases green, including all 7 new/changed cases
  • bash tests/fm-fleet-snapshot-view.test.sh - downstream consumer of the crew-state detail, green
  • Manual before/after E2E: bin/fm-crew-state.sh &lt;id&gt; run from base 8988af2 and from d813ec0 over four real axi status shapes (pr+ci skipped / pr completed+ci skipped / genuinely merged / findings row shadowing the pr step), via a hermetic fake no-mistakes and a real throwaway git worktree
  • Manual E2E of the active-run both-tables shape (ci,running,0,0 in steps beside ci,running,1m40s,... in active_steps) with a green ci log tail, confirming the green-checks override still fires
  • Regression proof: base-commit reader swapped in, then each new test run individually (test_terminal_passed_pr_skipped_claims_no_merge, test_terminal_passed_ci_skipped_claims_no_merge, test_terminal_passed_with_pr_as_last_step_claims_no_merge, test_terminal_passed_without_a_pr_row_claims_no_merge, test_findings_row_is_not_read_as_a_step_row) - all five fail pre-fix
  • Mutation A: nm_step_rows third column widened [0-9]+ -> [^,]+; test_findings_row_is_not_read_as_a_step_row fails and the reader emits run passed, PR step warning: ...
  • Mutation B: nm_step_rows third column narrowed to [1-9][0-9]* so an active run's ci,running,0,0 no longer matches; test_ci_monitoring_with_active_steps_table_still_reads_the_step_row fails with state: working · validating (running)
  • Downstream surfaces: bin/fm-bearings-snapshot.sh Underway doing row and bin/fm-fleet-snapshot.sh --json .tasks[].current_state rendered for all three passed-run shapes
  • git status --porcelain after every mutation/restore cycle - worktree clean at d813ec0
⚠️ **Document** - 1 info
  • ℹ️ bin/fm-crew-state.sh:316 - Evidence-count mismatch between the change's narrative and its code comments. The user intent states the active-run shape was "sampled on four different running steps" on v1.60.2 (eb4e379), but both evidence comments - bin/fm-crew-state.sh:316 and tests/fm-crew-state.test.sh:407 - say "sampled on three different running steps". The recorded decision transcript quotes exactly three verbatim captures (test, document, review, with durations 7s/18s/19s); the fourth duration mentioned there (1m40s) appears only as the illustrative fixture value the instruction introduced with "e.g.", so I could not confirm it as an observed fourth sample. I left the comments at "three" rather than raising them to "four", because that is the number the available evidence supports and inflating it would assert a capture I cannot verify. The author holds the captures and should reconcile the two: the PR body will be written from the intent and must stand on its own for a maintainer who will also read the code comment, so a maintainer comparing them currently sees the change's own evidence claim contradict itself. Fixing it is a one-word edit in whichever of the two is wrong (both comments if four is correct, the intent/PR body if three is).

🔧 Fix: record observed minute-scale durations in step-row evidence comments
1 info still open:

  • ℹ️ bin/fm-crew-state.sh:316 - Residual, and outside this phase's edit scope: the user intent still says the active-run shape was "sampled on four different running steps", while both evidence comments now say three - and the author has explicitly confirmed three is the true count (three distinct running step names: review, test, document) and that the intent narrative was wrong. Since the pull request body is written from the intent and must stand on its own for an upstream maintainer who will also read bin/fm-crew-state.sh:316 and tests/fm-crew-state.test.sh:407, the PR body must say three, not four; copying the intent verbatim would ship a PR whose own evidence claim contradicts the code it delivers. I can only edit documentation and doc comments, so I cannot correct the intent text or the PR body from here - flagging it for whoever authors the PR body.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

ypadhi and others added 4 commits September 2, 2026 19:09
…r it completed

fm-crew-state mapped a no-mistakes `outcome=passed` run straight to the fixed
detail "run passed: PR merged/closed". A run reaches outcome=passed once its
steps finish without failing, which includes a run whose `pr` and `ci` steps
were SKIPPED - such a run opened and merged nothing, yet the reader asserted a
merge it had never observed.

State the rule generally rather than special-casing one step: the merge claim
is made only when the run's own `pr` step AND every step it reports after that
one read `completed`. A skipped `pr` step means nothing was opened; a completed
`pr` step whose later step never ran means a PR was opened but never carried to
a merge. In both cases the detail names the step that stopped short instead of
asserting a merge. No step name after `pr` is hardcoded - the steps a merge
claim depends on are read from the run's own step order.

Generalises nm_ci_step_status into nm_step_rows/nm_step_status, keyed on the
NUMERIC third column so a findings row is never mistaken for a step row (the
findings table is emitted first, so a finding whose id is `pr` would otherwise
shadow the real `pr` step and report its severity as the step status), and adds
nm_passed_pr_detail.

Tests cover the skipped `pr` step, the completed `pr` step with a skipped `ci`
step, `pr` as the last reported step, a run with no `pr` row, the still-valid
merged case, and a negative case that fails if the findings-row guard is
removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016NATZQwEuv3ztPckNnUtSM
@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The merge-reporting fix is broadly sound, but the step parser should be constrained to the steps table before merging.

A valid numeric file path on a finding whose id matches pr passes the new global row predicate and shadows the genuine PR step, producing an incorrect terminal detail.

Files Needing Attention: bin/fm-crew-state.sh

Reviews (1): Last reviewed commit: "no-mistakes(document): record observed m..." | Re-trigger Greptile

Comment thread bin/fm-crew-state.sh
# about a version, not a law.
nm_step_rows() {
printf '%s\n' "$RUN_OUT" \
| grep -E "^[[:space:]]*[A-Za-z0-9_-]+,[[:space:]]*\"?[A-Za-z0-9_-]+\"?[[:space:]]*,[[:space:]]*[0-9]+[[:space:]]*,"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Numeric findings shadow PR steps

If a pr finding has a numeric file path such as 123, the global numeric-third-column predicate accepts it as a step row and nm_step_status selects it before the genuine pr row, causing a completed run to report the finding severity as the PR-step status and incorrectly mark the merge state unknown.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for flagging this - I checked it against real no-mistakes axi status output rather than reasoning about the format, and the shadowing path does not open on v1.60.2.

The predicate's third column requires bare digits ([0-9]+, no quotes accepted). The TOON encoder quotes a string field whose value would otherwise read as a number, so a findings row for a file named 123 renders quoted.

To confirm rather than assume, I put a file literally named 123 in a scratch repo and let the review step report on it. All four findings rendered like this (v1.60.2, eb4e379):

findings[4]{id,severity,file,action,description}:
    unquoted-dir-mass-delete,error,"123",auto-fix,"..."
    self-labeled-bug-comment,warning,"123",ask-user,"..."
    mtime-boundary-off-by-one,info,"123",ask-user,"..."
    extra-args-silently-ignored,info,"123",ask-user,"..."

The file column is "123" - quoted. Feeding those exact rows through the predicate rejects every one of them. The same encoder rule is visible elsewhere in the same output: agent_pid is a numeric-looking string and renders quoted ("185869"), while genuine numbers such as duration_ms render bare (0, 22991).

So the row shape your comment describes - pr,warning,123,auto-fix,..., with a bare numeric third column - would indeed be accepted by the predicate and would shadow the genuine pr row. But that is not a shape this version emits; it emits pr,warning,"123",auto-fix,..., which the predicate rejects.

One caveat worth stating plainly, since it is the honest limit of the evidence: this is a fact about one version's output shape, not a guarantee. If the encoder ever stopped quoting numeric-looking strings, the predicate would need a quote-aware third column. The negative test added in this PR (test_findings_row_is_not_read_as_a_step_row) pins the general property that a findings row must never be read as a step row, so a regression of that kind has somewhere to fail.

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