What happens
bin/fm-crew-state.sh can report a ship task as terminally finished — "PR merged/closed" — for a pull request that is open, red, and carrying an unresolved blocker. The same read then marks that still-open blocker superseded.
Observed in the field: a supervisor received a terminal monitoring result claiming a pull request was merged/closed and its history superseded. The forge disagreed on every count — the PR was open, not merged, mergeable_state: unstable, with a required check in conclusion: failure, and the preserved blocker still open. Cleanup was refused by the operator, which is the only reason the unlanded work survived.
Mechanism
Two adjacent paths compose into the false result.
1. The terminal PR claim is inferred, never verified (bin/fm-crew-state.sh:502):
passed) RUN_STATE="done"; RUN_DETAIL="run passed: PR merged/closed" ;;
The reader takes the validation pipeline's own outcome field and, on passed, emits a factual claim about the pull request — that it is merged or closed. No forge read happens on this path. When that outcome is wrong, stale, or sourced from a superseded run, the supervisor is told the PR reached a terminal state it never reached.
Contrast bin/fm-pr-poll.sh, which is correctly conservative: it emits merged only on an exact MERGED/merged state read from the forge and stays silent otherwise. The poll cannot produce this claim. The terminal language originates in the state reader, which infers where the poll verifies.
2. The contradicting blocker is invalidated by that same wrong state (bin/fm-crew-state.sh:575-577):
case "$LOG_VERB" in
needs-decision|blocked)
if [ "$RUN_STATE" != parked ]; then
... RUN_DETAIL="$RUN_DETAIL${SEP}status-log superseded (run $RUN_STATE)"
A preserved blocked record is treated as deterministically stale whenever the run is not parked — sound reasoning, but only when RUN_STATE is itself trustworthy.
3. Why this is one defect rather than two
Step 1 sets RUN_STATE=done from a wrong outcome. Step 2 reads that same wrong value and, because it is not parked, marks the open blocker superseded. A single bad outcome value produces both halves of the false terminal result, and the second half destroys the very record that would have contradicted the first.
That is the discard-risk shape: a false terminal state plus an auto-invalidated blocker is precisely the combination that authorizes teardown of unlanded work.
Why it matters
This fails in the dangerous direction. A reader that overstates trouble costs attention; a reader that reports "finished, nothing blocking" over work that is open, red, and blocked invites destruction of that work.
Relationship to existing issues
Same family — both are fm-crew-state.sh misreads — but neither covers this:
| Issue |
Claim |
Why it does not cover this |
| #3216 |
zero CI checks reported as checks green |
Different field and path (nm_ci_checks_state, CI log marker) |
| #3215 |
superseded cancelled run surfaced, reporting a parked task as failed |
Fails toward alarm; harmless direction, and no blocker-supersession coupling |
The distinguishing property is direction of failure: #3215 and #3216 make a task look worse or busier than it is. This one makes unlanded, red, blocked work look finished and safe to discard.
Reproduction shape
- A ship task reaches a validation run whose
outcome field reads passed while its pull request is still open with a failing required check.
- The task's status log carries an unresolved
blocked record.
- Read current state with
bin/fm-crew-state.sh <id>.
Expected: the reader does not assert a terminal PR state it has not verified against the forge, and does not supersede an open blocker on the strength of an uncorroborated run state.
Actual: run passed: PR merged/closed plus status-log superseded (run done).
Notes
- Evidence is described by shape and field values; the originating pull request is in a private repository, so its identifiers are withheld. Everything needed to locate the defect is in the two code paths above.
- Filed as a report only. No fix design is proposed here — two directions worth weighing are verifying the terminal claim against the forge before stating it (or not phrasing it as a claim about the PR at all), and not driving blocker supersession from a
RUN_STATE no forge read has corroborated.
What happens
bin/fm-crew-state.shcan report a ship task as terminally finished — "PR merged/closed" — for a pull request that is open, red, and carrying an unresolved blocker. The same read then marks that still-open blocker superseded.Observed in the field: a supervisor received a terminal monitoring result claiming a pull request was merged/closed and its history superseded. The forge disagreed on every count — the PR was open, not merged,
mergeable_state: unstable, with a required check inconclusion: failure, and the preserved blocker still open. Cleanup was refused by the operator, which is the only reason the unlanded work survived.Mechanism
Two adjacent paths compose into the false result.
1. The terminal PR claim is inferred, never verified (
bin/fm-crew-state.sh:502):The reader takes the validation pipeline's own
outcomefield and, onpassed, emits a factual claim about the pull request — that it is merged or closed. No forge read happens on this path. When that outcome is wrong, stale, or sourced from a superseded run, the supervisor is told the PR reached a terminal state it never reached.Contrast
bin/fm-pr-poll.sh, which is correctly conservative: it emitsmergedonly on an exactMERGED/mergedstate read from the forge and stays silent otherwise. The poll cannot produce this claim. The terminal language originates in the state reader, which infers where the poll verifies.2. The contradicting blocker is invalidated by that same wrong state (
bin/fm-crew-state.sh:575-577):A preserved
blockedrecord is treated as deterministically stale whenever the run is not parked — sound reasoning, but only whenRUN_STATEis itself trustworthy.3. Why this is one defect rather than two
Step 1 sets
RUN_STATE=donefrom a wrong outcome. Step 2 reads that same wrong value and, because it is notparked, marks the open blocker superseded. A single bad outcome value produces both halves of the false terminal result, and the second half destroys the very record that would have contradicted the first.That is the discard-risk shape: a false terminal state plus an auto-invalidated blocker is precisely the combination that authorizes teardown of unlanded work.
Why it matters
This fails in the dangerous direction. A reader that overstates trouble costs attention; a reader that reports "finished, nothing blocking" over work that is open, red, and blocked invites destruction of that work.
Relationship to existing issues
Same family — both are
fm-crew-state.shmisreads — but neither covers this:checks greennm_ci_checks_state, CI log marker)The distinguishing property is direction of failure: #3215 and #3216 make a task look worse or busier than it is. This one makes unlanded, red, blocked work look finished and safe to discard.
Reproduction shape
outcomefield readspassedwhile its pull request is still open with a failing required check.blockedrecord.bin/fm-crew-state.sh <id>.Expected: the reader does not assert a terminal PR state it has not verified against the forge, and does not supersede an open blocker on the strength of an uncorroborated run state.
Actual:
run passed: PR merged/closedplusstatus-log superseded (run done).Notes
RUN_STATEno forge read has corroborated.