Skip to content

fix(bin): bind crew-state run attribution to the task incarnation - #6245

Open
AgardnerAU wants to merge 9 commits into
kunchenguid:mainfrom
AgardnerAU:fm/fm-crewstate-refresh-inherits-old-run
Open

AgardnerAU wants to merge 9 commits into
kunchenguid:mainfrom
AgardnerAU:fm/fm-crewstate-refresh-inherits-old-run

Conversation

@AgardnerAU

@AgardnerAU AgardnerAU commented Sep 30, 2026 •

Copy link
Copy Markdown

Intent

On 2026-10-13 the captain approved updating 11 open upstream PRs, including #6245 ("yes, update those 11 PR's"). The approved update: merge current upstream main into each PR, fix the conflicts, run the tests, look into red checks, push, and post a short note on each PR.

Context: A read-only check on 2026-10-13 found this PR CONFLICTING with current upstream main (head ef782818), and its "Greptile Review" check is failing. Upstream kunchenguid/firstmate closes a PR after 14 days of AUTHOR silence; only a push or comment by the author resets that clock, and a stale close is irreversible. On 2026-09-18 the captain asked for every PR waiting on us in the kunchenguid repositories to be kept alive.

The PR is on branch fm/fm-crewstate-refresh-inherits-old-run from the fork AgardnerAU/firstmate into kunchenguid:main. Reuse that branch and that PR; never replace them.

The PR referred to above (#6245, "fix(bin): bind crew-state run attribution to the task incarnation") fixes false "checks green" reports: a refresh task that reuses an existing upstream PR branch read the previous task's finished no-mistakes run and was reported done and green before its own validation or the upstream CI had run. The PR records the task start (task_started=, carried across relaunches) in the task record, makes bin/fm-crew-state.sh attribute only runs created after that task start, and reports a finished run's PR state only from the forge or a merge receipt, naming the source of the claim.

What Changed

  • bin/fm-spawn.sh now records task_started=<epoch> in the task's meta on a fresh spawn. A relaunch keeps the earlier value. When an older record has no task_started=, the relaunch takes the epoch from the prior spawn_gen=.
  • bin/fm-crew-state.sh and bin/fm-nm-run-lib.sh only attribute no-mistakes runs created at or after the task's start, using the run's ULID timestamp and the creation minute in the runs ledger. If a branch's newest run is older than the task, as happens when a refresh task reuses an existing PR branch at an unchanged head, no run is attributed. Current pane or status evidence answers instead, and the line names the ignored run.
  • A finished run's record is no longer reported as "green" by itself. A PR state such as recorded PR open (forge) or recorded PR merged (merge receipt) comes from the forge (GitHub, GitLab, Gerrit, Forgejo) or a merge-poll receipt, and the line names that source. A merged claim needs the task's recorded pr=. A run PR that is different from the recorded pr= reports as a conflict. A CI log that shows no checks stays a wait. A worker status written after the run started is shown next to the run verdict. Tests in tests/fm-crew-state.test.sh, tests/fm-control-relaunch.test.sh and tests/fm-trace-context-spawn.test.sh cover this, and the docs (docs/architecture.md, docs/gerrit-change-watch.md, ship-landing skill) are updated to match.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: The range is mainly the upstream-main merge into the reviewed PR. The remerge-diff shows two conflicts, and both were resolved correctly: the new Forgejo provider now goes through the PR's pr_disposition reader, which handles merged as closed with merged true, and the relaunch-owned keys keep both base_branch and task_started. The Forgejo test expectations follow the PR's wording, which names the source of each claim. The other upstream changes (fm_nm_state_db, Forgejo fakes) sit alongside the PR's incarnation-binding logic and do not conflict with it. The ci-1 and ci-2 behaviours the user declined are unchanged and are not raised again.

Testing

Removed the committed scratch test file. Ran the change's own crew-state incarnation tests (13 pass) and the spawn and relaunch task-start tests (both pass). Drove the real fm-crew-state.sh CLI side by side against the base commit: base reproduces the false done/passed report for a refresh task, and this change reports working and names the ignored run. In a disposable lab home, a real Claude scout spawn recorded task_started= and a real relaunch kept it. All live-driven scenarios passed. The run-step wording scenario is covered only by the change's own tests and was not driven live. The permission layer refused deletion of the stopped lab home and the temp scratch dir under $TMPDIR. They hold only fixtures, and the lab tmux server was killed.

  • Live validation: ✅ go - 6 of 7 scenarios driven live against the product
Scenario Result Live Evidence
Refresh task on a reused PR branch whose earlier task left a passed run: crew-state reports working from the pane, not done/green ✅ pass live crew-state-before-after.txt S1: base says state: done run passed; change says state: working, source: pane, run predates this task: not attributed
Same refresh task while idle: the worker's status log answers ('resolving merge conflicts') instead of the earlier run ✅ pass live crew-state-before-after.txt S2
The task's own run created after task start is still attributed, and its PR state is named as coming from the forge ✅ pass live crew-state-before-after.txt S3: state: done · source: run-step · run PR open (forge)
Adversarial: a relaunched task keeps a run from its own earlier incarnation, and a legacy record without task_started is bounded by spawn_gen ✅ pass live crew-state-before-after.txt S4 (done, attributed) and S5 (working, predates this task); crew-state-incarnation-tests.log
A real fresh spawn records task_started= equal to the spawn_gen epoch ✅ pass live live-lab-spawn-relaunch-task-started.txt: task_started=1791888932 with spawn_gen=s1791888932...
A real relaunch creates a new spawn_gen and carries task_started forward unchanged ✅ pass live live-lab-spawn-relaunch-task-started.txt: after relaunch spawn_gen=s1791888966..., task_started=1791888932
Run-step wording never presents a run record as green, and a merged claim needs the recorded pr= identity ⏸️ untested no The prior payload did not establish a live result: this was covered only by the change's own fixture tests, not driven separately against the live product in that turn.
Evidence: fm-crew-state before (base 6407052) vs after (this change) transcript

Source: fm-crew-state before (base 6407052f) vs after (this change) transcript

S1 refresh task, earlier run 1h before task start [this change] state: working · source: pane · harness busy (claude-hook) · run 01M4TAWRFG... predates this task: not attributed [base 6407052f] state: done · source: run-step · run passed: PR open S3 own run after start [this change] state: done · source: run-step · ... run PR open (forge)

== S1: refresh task (task_started=now) whose branch has an earlier task's PASSED run from 1h ago; worker busy
$ fm-crew-state.sh refresh   # earlier run predates task
  [this change] state: working · source: pane · harness busy (claude-hook) · run 01M4TAWRFGABCDEFGHJKMNPQRS predates this task: not attributed 
  [base 6407052f] state: done · source: run-step · run passed: PR open · run: 01M4TAWRFGABCDEFGHJKMNPQRS 

== S2: same, worker idle with status line 'working: resolving merge conflicts'
$ fm-crew-state.sh refresh   # status log answers
  [this change] state: working · source: status-log · resolving merge conflicts · run 01M4TAWRFGABCDEFGHJKMNPQRS predates this task: not attributed 
  [base 6407052f] state: done · source: run-step · run passed: PR open · run: 01M4TAWRFGABCDEFGHJKMNPQRS 

== S3: the task's OWN run created 60s after task start, passed
$ fm-crew-state.sh refresh   # own run attributed
  [this change] state: done · source: run-step · run passed-with-override (approved exception, not green: live checks not all passed: Lint (fail)): run PR open (forge) · run: 01M4TECEPGABCDEFGHJKMNPQRS 
  [base 6407052f] state: done · source: run-step · run passed: PR open · run: 01M4TECEPGABCDEFGHJKMNPQRS 

== S4: relaunched task: task_started 2h ago, run created 1h ago (earlier incarnation of SAME task)
$ fm-crew-state.sh refresh   # relaunch keeps incarnation
  [this change] state: done · source: run-step · run passed-with-override (approved exception, not green: live checks not all passed: Lint (fail)): run PR open (forge) · run: 01M4TAWRFGABCDEFGHJKMNPQRS 
  [base 6407052f] state: done · source: run-step · run passed: PR open · run: 01M4TAWRFGABCDEFGHJKMNPQRS 

== S5: legacy record without task_started, spawn_gen=s<now> and earlier run
$ fm-crew-state.sh refresh   # spawn_gen bounds
  [this change] state: working · source: status-log · rebasing · run 01M4TAWRFGABCDEFGHJKMNPQRS predates this task: not attributed 
  [base 6407052f] state: done · source: run-step · run passed: PR open · run: 01M4TAWRFGABCDEFGHJKMNPQRS 
Evidence: Live lab spawn + relaunch task record

Source: Live lab spawn + relaunch task record

after spawn: spawn_gen=s1791888932.60665.1433 task_started=1791888932 after relaunch: spawn_gen=s1791888966.11490.24100 task_started=1791888932

# Live lab: real bin/fm-spawn.sh --scout (claude) then bin/fm-control.sh relaunch, disposable FM_HOME
## spawn
before=1791888925
spawned scout1 harness=claude kind=scout window=primary:fm-scout1 worktree=~/.treehouse/demo-35fc5a/1/demo
spawn rc=0
after=1791888935
## task record after spawn: spawn_gen=s1791888932.60665.1433 task_started=1791888932
## relaunch
relaunched scout1 harness=claude from=claude model=default effort=default backend=tmux endpoint=primary:fm-scout1 worktree=~/.treehouse/demo-35fc5a/1/demo
relaunch rc=0
## task record after relaunch
spawn_gen=s1791888966.11490.24100
task_started=1791888932
Evidence: crew-state incarnation test functions

Source: crew-state incarnation test functions

ok - a refresh task does not inherit an earlier task run on the same branch and head
ok - a refresh task's status log answers over an earlier task run
ok - a run created after the task started is this task's run
ok - a relaunched task keeps runs from its earlier incarnation
ok - the spawn generation bounds attribution when no start record exists
ok - a run whose creation time is unreadable is not attributed to a bounded task
ok - the legacy route does not inherit an earlier task run
ok - the coarse ledger does not inherit an earlier task run
ok - a head-anchor row in the task start minute still anchors the continuation
ok - a merged claim requires the recorded PR identity
ok - run-step wording never presents green
ok - a newer status line is shown beside a finished run
ok - a deliberate abort is distinct from a failure
all fm-crew-state tests passed
Evidence: spawn/relaunch task-start tests

Source: spawn/relaunch task-start tests

ok - fresh spawn: the task start is recorded with the first spawn generation
# all fm-trace-context-spawn tests passed
ok - fm-control relaunch: the task start carries across relaunches
- Outcome: ⚠️ 2 infos across 3 runs (1h8m55s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

⚠️ **Test** - 2 infos
  • ⚠️ The Test agent did not finish within its invocation budget. Reported: agent run tests timed out after 30m0s: agent last produced output 1s ago (92 observed); agent reported: claude parse events: context deadline exceeded. This is a budget or provider-slowness cut, not a code failure. Re-running the same request costs another full budget, so no further attempt is made automatically. If this repository's targeted tests or evidence gathering routinely approach the default 30m0s, raise test_agent_timeout in global config. Respond with fix to spend another budget: a repair turn runs only for selected findings other than this budget cut, then validation re-runs. Or abort and retry after raising the budget.
  • 🚨 Approval is refused: the run worktree at ~/.no-mistakes/worktrees/c272d8f3fc4c/01M4TAC36RQET9B6H5W16PEM08 holds work no Test turn validated, and the steps after Test would commit and publish it. It holds uncommitted changes to tests/.tmp-crew-subset.test.sh (inspect with git -C ~/.no-mistakes/worktrees/c272d8f3fc4c/01M4TAC36RQET9B6H5W16PEM08 status and git -C ~/.no-mistakes/worktrees/c272d8f3fc4c/01M4TAC36RQET9B6H5W16PEM08 diff). Respond with fix to validate it, or abort.

🔧 No changes applied.
3 issues (2 errors, 1 info) still open:

  • 🚨 tests/.tmp-crew-subset.test.sh - The operator asked for the Test agent's scratch file tests/.tmp-crew-subset.test.sh to be deleted and never committed. This turn's permission layer refused every attempt to remove it (rm tests/.tmp-crew-subset.test.sh and git clean -f -- tests/.tmp-crew-subset.test.sh were both denied), and I did not work around the denial. The file is still untracked in the run worktree, so later steps could commit and publish it. A human, or a turn allowed to delete files, must run rm tests/.tmp-crew-subset.test.sh in ~/.no-mistakes/worktrees/c272d8f3fc4c/01M4TAC36RQET9B6H5W16PEM08 before approval. All live validation scenarios passed; this is the only blocker.
  • ℹ️ Running tests/fm-crew-state.test.sh, tests/fm-trace-context-spawn.test.sh and tests/fm-control-relaunch.test.sh as whole files hit the 10-minute background limit and were stopped. No test had failed before the cut (164, 13 and 10 ok lines). Instead I ran the change's own test functions on their own: crew-state incarnation, relaunch carry-forward and the fresh-spawn task start. All passed. Remote CI still has to run the full files.
  • 🚨 live validation verdict: no-go (7 of 9 scenarios were driven live against the product)
  • Live validation: ❌ no-go - 7 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Refresh task on a reused PR branch, with an earlier task's finished passed run at the same head and a busy worker, reads working (not done or green) and names the ignored run ✅ pass live live-crew-state-transcript.txt S1: state: working · source: pane · harness busy (claude-hook) · run ... predates this task: not attributed; base commit gives state: done (baseline-crew-state-trans…
Same refresh task with an idle worker: its own status-log line answers, not the earlier run ✅ pass live live-crew-state-transcript.txt S2: state: working · source: status-log · resolving merge conflicts · run ... predates this task: not attributed
The task's own run, created after task_started, finishes passed: done from run-step, and the PR state names its source without claiming a merge ✅ pass live live-crew-state-transcript.txt S3: state: done · source: run-step · run passed: recorded PR state unknown (forge read skipped)
Relaunched task keeps runs from its earlier incarnation (task_started before the run, new spawn_gen after it) ✅ pass live live-crew-state-transcript.txt S4: state: done · source: run-step
Legacy record without task_started is bounded by spawn_gen ✅ pass live live-crew-state-transcript.txt S5: run ... predates this task: not attributed
Adversarial: a run created 1 second before the task start, and a run id that is not a ULID, are both refused ✅ pass live live-crew-state-transcript.txt S6 predates this task: not attributed; S7 run 01RUN creation time unreadable: not attributed
A CI monitor whose CI is held (no checks reported) reads waiting, not green ✅ pass live live-crew-state-transcript.txt S8: state: working · source: run-step · ci running: no CI checks reported (waiting for checks, not green)
Coarse runs-ledger fallback: a same-minute row is not attributed unless it is the head-anchor row ⏸️ untested no The prior payload did not establish a live result: this was covered only by targeted unit tests (test_coarse_ledger_does_not_inherit_an_earlier_run, test_coarse_ledger_anchor_row_in_start_minute_still…
A fresh spawn records task_started, and a relaunch carries it forward (derived from spawn_gen for older records) ⏸️ untested no The prior payload did not establish a live result: this was exercised only through tests running fm-spawn.sh and fm-control.sh with a fake tmux and harness (both ok). A live Claude spawn would write t…
  • bash live-crew-state-driver.sh &lt;worktree&gt; (evidence dir): the real bin/fm-crew-state.sh in a marked lab FM_HOME made by bin/fm-lab-home.sh create, with real fm-busy-event.sh busy/idle records, a real tmux pane on a private TMUX_TMPDIR socket, a disposable git worktree on fm/fm-crewstate-refresh-inherits-old-run, a stand-in no-mistakes CLI serving a ULID run record, and FM_CREW_STATE_NO_FORGE=1; scenarios S1-S8; lab torn down by an EXIT trap
  • bash baseline-wrapper.sh &lt;worktree&gt; live-crew-state-driver.sh 6407052f: the same driver against the base commit's bin/ to reproduce the bug
  • Targeted functions from tests/fm-crew-state.test.sh (run with the file's top-level checks; exit 0): test_refresh_task_does_not_inherit_an_earlier_run, test_refresh_task_status_log_answers_over_an_earlier_run, test_run_created_after_task_start_is_attributed, test_relaunch_keeps_the_task_incarnation, test_spawn_generation_bounds_a_task_without_a_start_record, test_unreadable_run_creation_time_is_not_attributed_to_a_bounded_task, test_legacy_route_does_not_inherit_an_earlier_run, test_coarse_ledger_does_not_inherit_an_earlier_run, test_coarse_ledger_anchor_row_in_start_minute_still_anchors, test_merged_claim_requires_the_recorded_pr_identity, test_run_step_wording_never_presents_green, test_newer_status_line_is_shown_beside_a_finished_run, test_deliberate_abort_is_distinct_from_failure
  • tests/fm-control-relaunch.test.sh functions test_relaunch_preserves_durable_task_metadata and test_relaunch_carries_the_task_start (exit 0)
  • tests/fm-trace-context-spawn.test.sh test_fresh_spawn_records_the_task_start (ok before the whole-file run was cut)
  • Attempted rm and git clean of tests/.tmp-crew-subset.test.sh: denied by permission

🔧 Fix applied.
2 infos still open:

  • ℹ️ tests/.tmp-crew-subset.test.sh - The previous Test turn had committed its scratch file tests/.tmp-crew-subset.test.sh (5581 lines) in d1d28d6. As the operator asked, this turn removed it with git rm. The deletion is staged and must be part of the next commit, so the file does not reach the PR.
  • ℹ️ The permission layer refused rm -rf of two disposable temp directories. They are the stopped lab home /var/folders/41/64hrmnwx11q5lw3l9d7zmfgr0000gn/T/fm-lab.lBvtt0 (its tmux server was killed and the task torn down) and the scratch runner dir /var/folders/41/64hrmnwx11q5lw3l9d7zmfgr0000gn/T/crewsub.VFw7eK. Both are outside the worktree and hold only fixtures. The lab spawn also returned a treehouse pool slot (~/.treehouse/demo-35fc5a) through fm-teardown. A human can remove these.
  • Live validation: ✅ go - 6 of 7 scenarios driven live against the product
Scenario Result Live Evidence
Refresh task on a reused PR branch whose earlier task left a passed run: crew-state reports working from the pane, not done/green ✅ pass live crew-state-before-after.txt S1: base says state: done run passed; change says state: working, source: pane, run predates this task: not attributed
Same refresh task while idle: the worker's status log answers ('resolving merge conflicts') instead of the earlier run ✅ pass live crew-state-before-after.txt S2
The task's own run created after task start is still attributed, and its PR state is named as coming from the forge ✅ pass live crew-state-before-after.txt S3: state: done · source: run-step · run PR open (forge)
Adversarial: a relaunched task keeps a run from its own earlier incarnation, and a legacy record without task_started is bounded by spawn_gen ✅ pass live crew-state-before-after.txt S4 (done, attributed) and S5 (working, predates this task); crew-state-incarnation-tests.log
A real fresh spawn records task_started= equal to the spawn_gen epoch ✅ pass live live-lab-spawn-relaunch-task-started.txt: task_started=1791888932 with spawn_gen=s1791888932...
A real relaunch creates a new spawn_gen and carries task_started forward unchanged ✅ pass live live-lab-spawn-relaunch-task-started.txt: after relaunch spawn_gen=s1791888966..., task_started=1791888932
Run-step wording never presents a run record as green, and a merged claim needs the recorded pr= identity ⏸️ untested no The prior payload did not establish a live result: this was covered only by the change's own fixture tests, not driven separately against the live product in that turn.
  • git rm tests/.tmp-crew-subset.test.sh (removes the scratch file committed by the previous Test turn, as the operator asked)
  • The 13 new task-incarnation test functions from tests/fm-crew-state.test.sh, run through a copy of the file in a temp directory outside the worktree (real bin/fm-crew-state.sh over real throwaway git repos)
  • Before/after CLI transcript: real bin/fm-crew-state.sh at HEAD and at base 6407052f (git archive), over the same fixtures for 5 incarnation scenarios
  • test_fresh_spawn_records_the_task_start (tests/fm-trace-context-spawn.test.sh) and test_relaunch_carries_the_task_start (tests/fm-control-relaunch.test.sh), run on their own
  • Live lab: bin/fm-lab-home.sh create, bin/fm-brief.sh scout1 demo --scout, real bin/fm-spawn.sh scout1 &lt;proj&gt; --scout --harness claude on the private fm-lab tmux socket, then bin/fm-control.sh scout1 relaunch, then bin/fm-teardown.sh scout1 --force, then tmux -L fm-lab kill-server
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

A refresh task that reuses an existing PR branch at an unchanged head read
the earlier task's finished no-mistakes run as its own result, so
fm-crew-state.sh reported "done - run passed: PR open" while the new worker
was still resolving conflicts.

- fm-spawn.sh records task_started= on a fresh spawn and carries it across
  relaunches; fm-nm-run-lib.sh owns the incarnation rule
  (fm_nm_run_in_incarnation, from the ULID run id's creation time, and a
  creation-minute bound on the runs ledger). A run created before the task
  started is never attributed, and the ignored run is named on the line that
  answers instead.
- A run record states only what the run did. A merged claim needs the task's
  recorded pr= identity checked against the forge or a merge receipt, every
  PR disposition names its source, and a run PR that differs from the
  recorded one is reported as a conflict.
- Run-step wording never presents green: a passed override names its
  exception, and a ci log reporting no CI checks reads as a wait.
- A worker status line written after a finished run started is shown beside
  the run-step verdict with its source.
- A deliberately aborted run names the abort and never reads as a failure.
@greptile-apps

greptile-apps Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 2/5

[High risk] Adds task incarnation tracking to run attribution logic.

The PR does not appear safe to merge while incomplete CI can still read as done and legitimate same-minute runs can disappear from coarse-ledger attribution.

Reviews (2) · Last reviewed commit: "no-mistakes(ci): Fixed ci-3 (Greptile fi..."

Comment thread bin/fm-crew-state.sh
passed-with-skips) RUN_STATE="done"; RUN_DETAIL="$(passed_pr_detail) (publication/CI verification skipped)" ;;
checks-passed) RUN_STATE="done"; RUN_DETAIL="checks green: PR ready for review" ;;
passed|passed-with-override|passed-with-skips) RUN_STATE="done"; RUN_DETAIL=$(passed_run_detail "$outcome") ;;
checks-passed) RUN_STATE="done"; RUN_DETAIL="run reports CI checks passed (required checks not verified)" ;;

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 Unverified checks finish the task

If Greptile Review is the only reported passing check while required CI is still held at action_required, a checks-passed outcome still produces state: done. The CI-log “checks passed” path does the same. Although the new wording says required checks were not verified, the task verdict still marks incomplete validation as finished and can produce the false-ready outcome this change aims to prevent.

Comment thread bin/fm-crew-state.sh
Comment on lines +1267 to +1268
if [ "$CI_LOG_STATE" = none-reported ]; then
emit_ship_status_done "run still monitoring PR; run ci log: no CI checks reported (not green)"

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 No-checks run still reads done

When the worker says done: PR … checks green but the run’s CI log says no checks were reported, this branch emits state: done from the worker’s claim. Adding “not green” to the detail does not undo the done verdict or remove the green claim, so a task still waiting for CI can appear finished.

Comment thread bin/fm-nm-run-lib.sh Outdated
…ute cross the task boundary), as the user instructed. The invariant: a row in the coarse `no-mistakes runs` ledger answers for a task only when its creation minute proves the row was created after the task started. The only exception is the head-anchor row, which proves code identity only. Minutes are compared in one place, `fm_nm_runs_status_for_worktree` in bin/fm-nm-run-lib.sh, and all three coarse-ledger callers in bin/fm-crew-state.sh go through it. At that one place I changed `[[ "$day $clock" < "$start_minute" ]]` to `! [[ "$start_minute" < "$day $clock" ]]`. Now a row created in the task's start minute prints nothing. The anchor-row check runs before this bound, so it stays exempt as before. I updated the function comment to match. Tests: test_coarse_ledger_does_not_inherit_an_earlier_run now also asserts that a completed row in the start minute at the same head does not give `source: run-step`. Without the fix, this assertion failed with `state: done · source: run-step · run completed`. The new test test_coarse_ledger_anchor_row_in_start_minute_still_anchors pins that a head-anchor row in the start minute still anchors an active continuation that follows it (run-step, working). The full tests/fm-crew-state.test.sh suite passes. Shellcheck shows no new findings. I did not change ci-1 or ci-2, because the user declined them. The failing Greptile check is a review bot, and its log was not available
Comment thread bin/fm-nm-run-lib.sh
fi
break
fi
if [ -n "$start_minute" ] && ! [[ "$start_minute" < "$day $clock" ]]; then

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 Current runs disappear from ledger

When a task starts and launches a run within the same minute, the coarse runs ledger gives both the same HH:MM timestamp. This comparison rejects the task’s own run. If the coarse ledger is the only available run identity, crew-state falls back to pane or status evidence instead of reporting the current validation run, potentially hiding its active or terminal result.

Resolve conflicts: route the new Forgejo provider through the PR's
pr_disposition reader, and keep both base_branch and task_started in the
relaunch-owned metadata keys. Align the upstream Forgejo crew-state test
with the PR's source-naming disposition wording.
@AgardnerAU

Copy link
Copy Markdown
Author

Merged current main into this branch and resolved the conflicts (new Forgejo provider routed through this PR's PR-state reader; relaunch-owned metadata keeps both base_branch and task_started). It is up to date with main and ready for CI; the workflow runs for head e8ca2a5 are waiting for maintainer approval.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: thank you @AgardnerAU. This is my first triage pass on this PR. I read the whole thread, the Greptile review and the tip diff of e8ca2a5a against main 6407052f.

What I checked:

  • bin/fm-spawn.sh records task_started= on a fresh spawn and carries it across relaunches. For older records it falls back to spawn_gen=.
  • bin/fm-crew-state.sh and bin/fm-nm-run-lib.sh only attribute runs created at or after that start. A refresh task on a reused PR branch at an unchanged head no longer reads the previous task's passed run as its own.
  • pr_disposition reports a finished run's PR state only from the forge or a merge receipt, and names the source. A merged claim needs the recorded pr=. A different run PR is reported as a conflict.
  • A run log with no CI checks is now a wait, and run-reported passes say the required checks were not verified.
  • No workflows or secrets are touched. Forge reads go through the existing bounded readers. No security concerns.

The no-mistakes attestation matches e8ca2a5a. Live validation in the body is go, 6 of 7. Run-step wording is fixture-tested only, which is fine for a wording rule. I approved the held fork runs: CI 38387095009 and Require no-mistakes 38387094975. If the run fails only on main's current Pi type drift (tests/fm-pi-primary-types.test.sh), that isn't on you.

Contract-class: new-default. The core fix is close to restoring an existing promise: main already says a historical run on a reused branch must not be attributed. The tip also replaces specified default readings, though. Main's tests had a terminal no-checks ci-monitor run reading done, and that is now a wait. Main reported checks green: PR ready for review, and that wording is replaced. Every spawn also writes a new task_started= record. Under our rules that makes the PR new-default, so it goes to Kun's merge-or-leave and won't auto-merge.

Open reviewer points for Kun to weigh. These aren't asks:

  • Greptile notes that a checks-passed run, or a CI log that reports a pass, still reads state: done while required checks are unverified. The body says that behaviour was a deliberate choice.
  • With strict after-start comparison, a task's own run started in the same ledger minute is not attributed from the coarse ledger. It falls back to pane or status evidence, which fails safe.

VISION.md:

  • One captain: aligns. It removes a false "done/green" report.
  • Authority: aligns. Evidence isn't a merged claim without the recorded PR.
  • Scripts/judgment: aligns. The binding is deterministic, by ULID and ledger minute.
  • Restart: aligns. task_started lives in durable meta and survives relaunch.
  • Delegation with a spine: aligns. A worker can't inherit someone else's validation.
  • Vendor: aligns. One reader serves GitHub, GitLab, Forgejo and Gerrit.
  • Scope: aligns. It reads no-mistakes records without taking over validation.

Overlaps with your other open PRs: #3701 changes the same run-reading logic in fm-crew-state.sh and fm-nm-run-lib.sh. #3281, #3989, #5435 and #6249 touch the same files. Whichever lands second will likely need a rebase.

Status: waiting-ci. Once CI and no-mistakes are green on a green main, this goes to Kun to merge or leave. Nothing is needed from you right now.

…and I fixed it in the test. The failing assertion was "metadata changed beyond Herdr container IDs between opted-out and projected paths" in tests/fm-backend-herdr-presentation-e2e.test.sh. Invariant: when the test compares the meta of two separate spawns, it must mask every field that changes with each spawn. This PR's bin/fm-spawn.sh now writes task_started=<spawn epoch> on every fresh spawn. This value is the same kind of field as spawn_gen=, which normalize_meta already masks. When the two spawns fall in different seconds, the line differs and the comparison fails. Fix: normalize_meta now also replaces ^task_started= with a placeholder. I searched the tests and no other test masks spawn_gen for a meta comparison across spawns, so no other place needs the same change. Verification: the full real-Herdr e2e passed locally on Herdr 0.9.1 (exit 0), including the metadata byte-identity check. CI ran Herdr 0.7.4, so the next CI run will confirm that version. The test's temp-directory cleanup printed some "Directory not empty" warnings from rm. The CI log shows a similar rm failure in this test, and the warnings did not affect the exit code. ci-1 (Behavior portable parallel 1) is not caused by this PR, and I made no code change for it. The failing test is tests/fm-pi-primary-types.test.sh. CI installs the unpinned latest @earendil-works/pi-coding-agent (1.2.0), and tsc reports that 'prompt' does not exist on BeforeAgentStartEvent in fm-branch-supervision.ts and fm-primary-pi-watch.ts. This PR does not change any .ts file. Upstream main CI at the base commit 6407052 also fails this job, so this is drift from a new Pi release upstream
@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: thanks @AgardnerAU. a5ab2877 is a one-line test fix: the Herdr presentation e2e meta comparison now masks the new task_started= the same way it already masks spawn_gen=. That makes sense, and I see no security concerns. The no-mistakes attestation matches a5ab2877. I approved the held fork runs: CI 38425734991 and Require no-mistakes 38425734927.

Since then main has moved. #7172 landed and touched bin/fm-spawn.sh, so this PR is now CONFLICTING. When you have a moment, could you merge main and re-raise through git push no-mistakes?

Contract-class: new-default (unchanged from my last pass). The incarnation binding is close to restoring an existing promise. But terminal no-checks runs moving from done to wait, the "checks green" wording, and the new always-on task_started= record field change specified defaults. VISION.md: every rule still aligns. The new commit changes only a test.

Status: waiting on you for the conflict only. After that, once CI is green on a green main, this goes to Kun to merge or leave. Nothing else is needed from you.

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