Skip to content

Sync fork with upstream (2026-09-26) - #11

Merged
babbarc merged 97 commits into
mainfrom
fm/fork-sync-20260926
Sep 26, 2026
Merged

babbarc merged 97 commits into
mainfrom
fm/fork-sync-20260926

Conversation

@babbarc

@babbarc babbarc commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Merges upstream kunchenguid/firstmate main (bb69be62) into the fork's main (fc5ef694) as a real merge commit. Upstream is 95 commits ahead.

Conflict resolution

15 files conflicted; every one was a both-sides addition, and none needed a judgment call.

  • Forge plumbing (bin/fm-pr-lib.sh, bin/fm-pr-check.sh, bin/fm-pr-merge.sh, bin/fm-pr-poll.sh, tests/fm-pr-check-security.test.sh, docs): upstream added Gerrit as a forge (feat(bin): publish and watch Gerrit changes on forge-bound projects kunchenguid/firstmate#5427) in the same places the fleet's Gitea/Forgejo provider lives. Both are kept. The URL parser tries GitHub, GitLab, Gerrit, then Gitea. The fleet code now uses upstream's rename fm_pr_gitlab_host_valid -> fm_pr_forge_host_valid. The merge script refuses Gerrit (upstream) and keeps the Gitea path unchanged. The poll has both provider arms. The test fixture adds both the tea fake and the gerrit-axi/no-mistakes fakes.
  • bin/fm-bootstrap.sh: upstream moved the secondmate liveness probe into the new bin/fm-secondmate-liveness-lib.sh (fix: auto-relaunch dead secondmates during supervision kunchenguid/firstmate#5496). The fleet's "alive but unsupervised" check (fix(bin): keep restored second mates supervised on their own home #8) is ported onto the new alive branch. It still applies only to local secondmates, as before.
  • bin/fm-dod-lib.sh: upstream rebuilt the DoD blocks per forge. The fleet's origin + tea wording goes back into the new direct-PR block. Upstream's gh-axi draft-check sentence stays word for word, because tests/fm-brief.test.sh and tests/fm-dod-lib.test.sh check that exact text.
  • AGENTS.md, README.md, docs/*: both sides' lines are kept. Upstream reorganized the FM_HOME section of docs/configuration.md and turned the liveness and nudge prose into bullets. The fleet's sentences are reapplied inside the new structure, not left as duplicates.
  • tests/fm-secondmate-liveness.test.sh: the fixture home now carries the fleet's healthy supervision records and is also upstream's git repo.

Gates

  • bin/fm-lint.sh: pass.
  • bin/fm-doc-audience-check.sh: pass.
  • Bounded targeted tests on the hand-resolved surfaces: tests/fm-pr-check-security.test.sh, tests/fm-pr-merge.test.sh, tests/fm-secondmate-liveness.test.sh (including the fleet's alive-but-unsupervised cases), tests/fm-dod-lib.test.sh, and tests/fm-brief.test.sh all pass.
  • The full suite is left to this PR's GitHub checks. The "PR must be raised via no-mistakes" check is expected to fail on fork syncs.

Carried-patch audit

Survive (still fleet-only, re-applied on top of upstream):

  • Gitea/Forgejo as a third PR forge (bin/fm-pr-*, bin/fm-teardown.sh, bin/fm-backlog-transition-lib.sh, bin/fm-nm-run-lib.sh, bin/fm-config-inherit-lib.sh, config/gitea-instances, docs/gitea-merge-watch.md, tests). The upstream PR kunchenguid/firstmate#3806 is still open, so this stays carried. This cycle's Gerrit work made the carry bigger: it now sits beside a fourth provider in every forge switch.
  • fix(pr-poll): tolerate control_relaunch_tx= after pr=/pr_head= #6: control_relaunch_tx= tolerated after pr=/pr_head= in fm_pr_metadata_identity_parse. Upstream fm-control.sh/fm-spawn.sh still write that key after pr=, and upstream's allowlist still lacks it, so the bug is live upstream.
  • docs: ship the kept backpass instruction edits #7: kept backpass edits to AGENTS.md and diagnostic-reasoning (FM_HOME check at session start, calling scripts by absolute path, recorded vs live head before merge, gate exit status, bullet formatting).
  • fix(bin): keep restored second mates supervised on their own home #8: keep restored secondmates on their own home (bin/fm-home-lib.sh, .pi/extensions/lib/fm-home-resolve.ts, fm_supervision_home_verdict, and the session-start run for a resume without the lock). Upstream has none of this.
  • http(s):// PR-URL wording in AGENTS.md and bearings (part of Gitea support).

Superseded or already upstream:

Worth sending upstream (not opened in this task):

Follow-up noticed (pre-existing on the fork, not introduced here): the Gitea branch of bin/fm-pr-merge.sh does not take hold_away_record_for_merge before the forge call, unlike the GitHub and GitLab branches. Also, bin/fm-crew-state.sh has no Gitea arm, so a passed run with a Gitea PR reads "PR state unknown".

Carries

bb69be62 fix: require declared waits for workers awaiting their own work (#5812)
9d56cf65 fix: stop cancelled validation runs from reporting false failures (#5815)
cf20836f fix(bin): pass the dispatch profile effort to OpenCode workers through their launch config (#5799)
e9a6675e fix(bin): gate a self-announcing tool's update-available report on a newer version (#5786)
062d7a87 fix(bin): report the newest status event in the voice status reader (#5790)
3948170a fix(bin): name the recovery for a declined Claude imports dialog and stop calling Escape safe there (#5791)
d1a332cd fix(bin): avoid bash 5.2 sibling $() in recovery mint and delivery log (#5773)
72b63eee fix(bin): dedup directed source expansions in fm-pending-reply-lib (#5753)
9b52cf5e feat: add attended supervision for Claude and Cursor hosts (#5748)
65c53fb6 Seed the relaunch-ordering PR poll fixture without fm-pr-check.sh (#5758)
920a7d98 fix: preserve Herdr status on Pi relaunch (#5161)
ea7c7f70 fix: stage remote home clones before publication (#5733)
873c9234 test: make supervision-host park-boundary tests deterministic (#5710)
df4ae5d6 fix(bin): stop nested steal-lock recursion and mid-steal watcher TERM (#5728)
9a4cfbb3 fix(bin): give slow watcher suites headroom under the changed-suite bound (#5516)
e789e522 fix(bin): republish parent metadata after a remote secondmate relaunch (#5583)
ef595d8d test: isolate lint fixture from tracked suite (#5727)
1fe1a679 fix(bin): deliver Claude-bound operational input as a record-backed doorbell (#5664)
c33b3f63 fix(bin): retire check-row receipts on branch acknowledgement so away escalations are not repeated (#5731)
4299683d feat: record the Claude and Cursor dialog for the supervision host (#5707)
f1ea9ed5 fix(bin): bind inactive-outcome receipt identity to structured fields only (#5520)
c4c9d639 fix(bin): use gh-axi for the ship DoD draft check (#5519)
97365aa2 fix(bin): absorb routine secondmate working and paused status appends (#5535)
3c14a549 fix: pause broken supervision-host sessions between engine probes (#5701)
f54aa000 fix: reject invalid X reply and follow-up arguments before posting (#5702)
0154324f fix: keep persistent secondmates out of landed-work cleanup (#5696)
8d2ee291 fix(bin): refuse merges with unreported required checks (#5534)
67f6c278 fix(bin): treat Pi's dollar-first cost footer as furniture (#5683)
83a4bbdb fix(bin): strip AI co-author trailers from fleet-launched commits (#5695)
b805823a fix(bin): classify shell stdin payloads after -s operands (#5546)
d1abcd6c fix(bin): match whole multi-word project names in the registry lookup (#5659)
c643b577 fix(bin): stand down the Claude Stop auto-arm on pi-code-delivered payloads (#5657)
e97390ae fix(bin): report the failing item when remote inheritance fails (#5658)
84362f65 fix(bin): surface unrecognized status prefixes instead of reading them as silence (#5588)
1a814e49 fix(bin): refuse watchers from disposable checkouts and exit when the home is gone (#5552)
dbe124d5 test: add a gated harness seam and stabilize lifecycle fixtures (#5638)
683b3eb9 fix(bin): bound the away digest and log why a delivery failed (#5554)
c60f0ab6 docs: make remote-secondmates easier to read (#5612)
7c501a1c docs: make captain-hold-lifecycle easier to read (#5610)
70e41a81 docs: make sessionstart-nudge easier to read (#5609)
d18a220f docs: make watcher-continuity easier to read (#5608)
5323582d docs: make pi-supervision-branch easier to read (#5607)
660fa4a2 docs: make the Herdr backend guide easier to read (#5606)
4e99de71 docs: make supervision-host easier to read (#5605)
756f64eb fix(bin): refuse teardown when a required source disappears (#5548)
5cec4e2b fix(pi): hide queued Firstmate inputs under Calm only when the session can keep them (#5563)
b575497a fix(bin): evict a watcher whose beacon stalls past a hard bound instead of refusing every re-arm (#5594)
ca8c293e feat: permit gate lifecycle calls against disposable lab homes (#5635)
b52d4018 fix: limit project memory edits to factual corrections (#5636)
4be8a409 docs: make configuration settings easier to find and understand (#5589)
a8572f62 fix(bin): terminate a remote job worker that lost ownership on TERM (#5544)
c6e816ff fix(bin): keep a stated default-key retraction from cancelling a keyless wait (#5587)
52e679b6 fix(bin): stop repeating unknown-wake escalations that were already delivered (#5599)
b42d4fa8 fix(bin): report a Lavish source armed only after its listener is running (#5566)
31c47af5 fix(bin): start a successor when the Claude Stop-hook arm's attached cycle ends (#5550)
d1ce6b6c fix: auto-relaunch dead secondmates during supervision (#5496)
e1b7f4f5 feat: extend opt-in away supervision to non-Pi primaries (#5503)
977a81ef fix(bin): refuse tasks-axi add/create --start so In flight always has a dispatch record (#5524)
0d983d2a fix(bin): forbid administering the shared worktree pool in crewmate briefs (#2868)
474c6ee2 fix(bin): ignore fenced and indented Captain lines when extracting authorized intent (#5526)
e1d6cf99 fix(bin): make the ps-fallback watcher identity immune to terminal width (#5517)
5842d423 fix(bin): bound the startup-network worker's lock waits by its budget (#5528)
d4f3b78e docs: correct the Grok harness reference on folder trust, training opt-in, and delivery (#5506)
9284978f feat: add opt-in Claude away supervision host (#5488)
795e4b58 feat(bin): send dispatch router only the brief's task sections and add per-rule confidence floors (#5478)
67130f18 feat(bin): make the ship-branch prefix configurable per project (#2648)
ac2ed3b2 fix: enforce supervision guards across harnesses (#5471)
5bbb978c fix(bin): keep Herdr lab session selection before passthrough arguments (#5470)
0afc6b4d feat(bin): opt-in per-home Claude and Pi worker account pin (#5358)
fdd36879 feat(bin): publish and watch Gerrit changes on forge-bound projects (#5427)
1d3ac679 fix(bin): refuse a Herdr Claude submit that would send only a message tail (#5336)
5df1294f test: make sibling secondmate stall tests wait for watcher observations (#5389)
9296f9b9 test: align portable test expectations with resolved host paths and fixture readiness (#5392)
7e0e60a2 fix(bin): prune a torn-down task's wake rows at teardown (#5390)
8c47279f test: isolate the bearings render fixture from the shared Lavish store (#5391)
1e0e7734 test: synchronize foreign queue stall checks with watcher progress (#5386)
c00d5e1e feat: record fleet status immediately and emit PR-ready events (#5385)
f0da72c5 test: close pr-check watcher test gaps (original flake already fixed by #5362 and #4878) (#5381)
fef37b95 fix: keep watcher status classification bounded to new log spans (#5383)
77e5af9b test: repair base-red liveness, export-DOM, and wake-queue self-tests (#5338)
2efa5812 fix(bin): refuse unavailable backend adapters before sourcing (#5382)
7c8f9eec fix(bin): recognize passed-with-skips as a passing outcome (#5322)
697d94d9 feat: add Devin CLI crewmate and scout adapter (#5380)
e7cb23e6 fix: validate public follow-up deliverables and wake on rejection (#5352)
f2ab14ad feat: add opt-in fleet activity ledger (#5375)
c576c2bb fix: submit stuck inbox doorbells instead of skipping them (#5374)
52fca517 fix: stop watchers reliably during blocked polls (#5362)
a5d78f8b test(watch-arm): size re-arm waits off the real loaded recovery cost (#5335)
82dec2ee fix(bin): ring a proven-idle secondmate before raising a wake-loop stall alarm (#5204)
a8a2959c fix(bin): refuse ship done: when the named head exists only in the worker copy (#4878)
706254de fix: deliver failed public follow-ups with updated AXI floors (#5350)
39f4c2af fix(bin): stop each keyed answer from re-waking this home (#4907)
dd9f2b4e fix(bin): stop secondmate relaunch failing when watcher scratch files vanish (#4900)
884d76bd fix: derive Lavish polling route from board session (#5334)
d92cea0c fix: surface green no-mistakes PRs awaiting merge (#5327)

kunchenguid and others added 30 commits September 22, 2026 14:19
* fix(bin): surface a green no-mistakes PR still in ci merge monitoring

A green PR could sit unreported because neither the worker nor the
supervisor could observe checks-green while the ci step kept monitoring
for the merge.

Supervisor read: fm_nm_select_run's capped-overview inventory reader looked
the repository up by the task worktree path, but no-mistakes registers a
repository once by its main clone path and resolves every linked worktree
to it, so on every task copy of a busy repo the lookup matched no row and
each read reported "complete same-branch run inventory unreadable". Key the
lookup on the overview's own top-level `repo:` line, which every axi
release emits as the resolved working_path.

Even with a readable run, the ci-log classifier treated "base branch
advanced ..., re-arming CI monitor timeout" as not-ready. The monitor logs
a checks state only when it changes and a base advance does not clear
readiness, so a green PR read as still validating for as long as main kept
advancing. Stop treating that line as a marker, matching no-mistakes' own
ci-log parser, and name the run's PR URL in the held-for-merge reading so
the existing inactive-outcome path can act on it without a worker report.

Worker contract: `axi status` never reports checks-passed while the ci
step monitors for merge, so the definition of done no longer makes a
status poll the wait for the next gate or outcome; the drive call's own
return is the green signal, reattached with `no-mistakes axi run` after a
bounded return.

* no-mistakes(review): read the full ci log when checking checks-green

* no-mistakes(review): correct stale ci log tail wording in docs

* no-mistakes(document): Document checks-green supervisor fallback
* fix: derive Lavish polling server from its board session

* no-mistakes(document): Document session-derived Lavish polling

* no-mistakes(document): Correct Lavish routing verification claims
… vanish (kunchenguid#4900)

* fix(bin): ignore vanished state scratch files on secondmate relaunch

Relaunch refused when find(1) exited non-zero while listing a secondmate
home's state directory. A live watcher can delete scratch files between
readdir and processing, which is not evidence that child *.meta records
are unreadable.

Prove the directory is listable from its mode and keep the existing
readable-meta loop as the child-record guarantee. Fixes kunchenguid#4765.

* no-mistakes(review): Skip chmod-000 unlistable-state relaunch test when running as root
…d#4907)

* fix(bin): treat home-owned status closes as already read

Self-announced bookkeeping appends now record their exact byte ranges.
Later drains and signal scans skip those ranges, so two distinct
--resolve-key answers after an OPEN DECISIONS fold do not each wake the
supervisor. Worker-authored lines outside that ledger still signal.

* no-mistakes(review): Keep owned closes in unread status; lock ledger writes

* no-mistakes(review): Drop fold-lag wake suppression so folded worker decisions still wake

* no-mistakes(review): Require real owned growth before ledger marks status seen

* no-mistakes(document): Clarify home-appends ledger scope versus UNREAD STATUS

* no-mistakes(review): Restore fold-lag path, drop owned-range filters, fix test

* no-mistakes(review): Align ledger docs and scope ledger to wake path only

* no-mistakes(review): Restore stranded historical-annotation test comment to its function

* no-mistakes(review): Retire the home-appends lock alongside its ledger

* no-mistakes(document): Note ledger's lock-helper dependency in classify library

* no-mistakes(review): Append-and-coalesce home-appends ledger; fix stamped-line assertions

* no-mistakes(review): Drop redundant empty-span branch; make owned test pin ledger

* no-mistakes(document): Document covers' ascending-order dependency on home-appends ledger

* no-mistakes(document): Note owned-append skip in watcher signal-scan comment
…nguid#5350)

* chore(bin): raise tasks-axi, quota-axi, and lavish-axi floors to latest

Raise the minimum versions to tasks-axi 0.2.6, quota-axi 0.1.50, and
lavish-axi 0.1.77, pin CI's tasks-axi install to 0.2.6, and move the
floor-boundary test fixtures to the new versions.

tasks-axi 0.2.6 makes a failed relation deliverable for a promised-final
expecting pr-merged, so add the regression test: a bound work that ends
failed reports its honest outcome text through fm-public-followup-emit.sh,
consume marks the commitment ready, and deliver posts that text exactly
once.

Also make two hang-guard tests in fm-backlog-atomicity portable to hosts
without coreutils timeout, and stop an installed herdr from leaking into
the secondmate-liveness husk classifier test.

* no-mistakes(review): drop out-of-scope bounded_run hang-guard helper from atomicity test

* no-mistakes(review): pin quota-axi floor at 0.1.49 across fixtures

* no-mistakes(document): Document failed public-followup delivery behavior

* no-mistakes(ci): Updated quota-axi floor and all 0.1.49 fixtures to 0.1.51, corrected bootstrap boundaries to 0.1.51/0.1.52/0.1.50, and bumped the bearings lavish-axi stub to 0.1.77. Bearings, quota procevent, quota chooser, startup budget, and bootstrap floor coverage passed; the full bootstrap suite exceeded the 240-second local command limit after relevant checks passed. git diff --check passed
…rker copy (kunchenguid#4878)

* fix(bin): refuse ship done: when the named head lives only in the worker copy

A ship done: is not current-state done until that exact commit is reachable
outside the disposable copy. The check tests the named head, not whether
some branch moved.

* fix(bin): gate CI-ready ship done: on named-head reachability, not handoff

Keep no-mistakes' first done: as the pipeline handoff, apply the same shared
check when registering a PR and when a secondmate publishes ledger-first,
treat a recorded merged PR as landed after prune, and name the PR head
instead of scanning free-text SHAs.

* no-mistakes(review): Bind named-head gate to recorded PR and forge heads

* no-mistakes(review): Gate direct-PR forge heads and keep pending ledger deliveries

* no-mistakes(review): Align worker done wording, test mapping, pending-retry test

* no-mistakes(test): Raise watcher test time limit to stop load flake

* no-mistakes(document): Restore ledger-path fact and name named-head gate coverage

* ci: re-attest named-head ship-done gate for a fresh serial-3 verdict

* no-mistakes(review): Simplify local-only gate, gate keyed done lines, document recovery

* no-mistakes(document): Name fm-crew-state among named-head gate callers
…all alarm (kunchenguid#5204)

* fix(bin): ring a proven-idle secondmate before a wake-loop stall alarm

A leftover foreign-queue row on an idle, alive, ring-safe mate is still drainable in that home. Ring once, reset the observation interval, and keep the parent alarm for unknown, busy, or still-frozen rows.

* no-mistakes(review): Mark drain steer with from-firstmate fire-and-forget carrier
…unchenguid#5335)

The re-arm recovery cases judged "the watcher stayed live instead of
surfacing recovery" with fixed budgets below what a real stale-lock
recovery costs on a contended host: the arm's default 10s confirmation
deadline, a start helper that returned after about 4s whether or not the
arm had confirmed its watcher, and an 80-poll exit wait.
A changed-suite run beside other suites starves the recovery's many
short-lived processes while this suite's sleeping poll loops keep their
pace, so a watcher still surfacing its recovery read as one that stayed
live (issue kunchenguid#3793).
The original 0.25s window after confirmation was widened to 80 polls in
kunchenguid#3837, which left the same race at a larger size.

Following the CONTRIBUTING.md fixture-budget rule, the re-arm helper now
gives the arm an explicit 30s confirmation budget and waits for its
confirmation or exit within a ceiling that outlasts it, and every wait on
a re-armed watcher uses one named iteration-counted ceiling that outlasts
the same budget.
A passing case returns as soon as the arm reports or exits, and a watcher
that never surfaces its recovery still fails.

A new case delays every mktemp and readlink the re-armed watcher runs
after it publishes its beacon, so its first poll and exit take about 13s
on any host.
It fails with the reported symptom on the previous budgets and passes now.
No bin/ change.
* fix(bin): let one TERM always stop the watcher on bash 5.2

Bash 5.2 runs a pending trap from the parser entry of the next command
substitution it expands, where the trap body is parsed as the inside of
that substitution and fails ("trap: line 2: unexpected EOF while looking
for matching `)'") or is dropped silently, consuming the signal. The
watcher's `trap 'exit 1' HUP INT TERM` could therefore ignore a TERM and
keep polling while its stopper waited: the triage suite's reap waited
forever (CI jobs cancelled at 30 minutes), and the arm's signal path and
the away-mode daemon's shutdown wait for the watcher the same way.
Bash 5.3 fixed the parser; 5.2 is the stock bash on Ubuntu 24.04.

HUP and TERM now keep bash's native fatal-signal handling, which runs the
EXIT trap (watcher_cleanup) and exits on bash 3.2, 5.2, and 5.3. INT keeps
its trap because bash ignores a direct SIGINT while a child runs. The
check-spawn deferral window no longer contains a command substitution.

The triage suite's reap is now bounded and fails the case within 10s with
process evidence instead of hanging the job, and a new regression test
proves TERM stops a watcher blocked inside a poll's pane capture and still
releases its lock and records an acknowledgeable stop.

* no-mistakes(document): Clarify watcher stop-signal documentation
…id#5374)

* fix(bin): submit our own stuck doorbell instead of skipping every later ring

* no-mistakes(review): Confirm and retry Enter once on stuck-doorbell submit

* no-mistakes(document): Clarify doorbell retry and pending-composer documentation
* feat(bin): add the opt-in fleet activity ledger

Homes that create config/fleet-ledger get an append-only JSONL file,
state/fleet-ledger.jsonl, recording task.dispatched, task.status,
task.merged, and task.cleaned_up so outside tools can follow a fleet.
With the flag absent each producer does one file test and nothing else.
docs/fleet-ledger.md owns the record contract and its documented limits.

* no-mistakes(review): Record task.status text verbatim after the first colon

* no-mistakes(document): Clarify fleet ledger status and setup documentation

* no-mistakes(ci): Fixed a timing race in tests/fm-pi-branch-extension.test.sh: the replacement-wake test now waits for the prompt to start before releasing it. The focused test passed twice, and git diff --check passed
…nchenguid#5352)

* fix(bin): format, validate, and surface public-followup deliverables

brief pre-fills report_path=data/<work-id>/report.md and states the accepted
format of every value it cannot know instead of a bare <value> placeholder.
fm-public-followup-emit.sh refuses a deliverable tasks-axi would refuse, in
both the direct and staged destinations, naming the key, value, and format.
consume records the specific deliverable, outcome, or missing key behind a
tasks-axi refusal, and each refusal wakes the owning home once through the
existing relay poll.

* no-mistakes(review): refuse emits missing a required deliverable in both destinations

* no-mistakes(review): require promised deliverables and keep rejections recoverable

* no-mistakes(review): mirror tasks-axi's canonical pull request URL rule

* no-mistakes(review): keep a rejection wake whose line cannot be read

* no-mistakes(review): key emit-time rules on the promise, not the outcome

* no-mistakes(review): bound deliverable keys and values as tasks-axi does

* no-mistakes(review): state rejection wakes as at-least-once and pin it

* no-mistakes(review): enforce the promised contract tasks-axi holds at emit

* no-mistakes(review): stop inferring a staged promise from its outcome

* no-mistakes(document): Refresh public follow-up documentation

* no-mistakes(ci): Fixed both CI flakes. Watcher cleanup is now installed before singleton acquisition, preventing timeout races from leaving stale locks while preserving recovery-failure evidence. Bearings render fixtures now publish a valid isolated Lavish session store and retire each listener after rendering, eliminating false unowned-source races. Verified with checkpoint stress, fm-watch-checkpoint, fm-watcher-lock, repeated fm-bearings-board-render runs, project lint, syntax checks, and git diff checks

* Revert unrelated CI auto-fix edits to the watcher and bearings board test

The CI step's automatic repair changed bin/fm-watch.sh and
tests/fm-bearings-board-render.test.sh to chase two intermittent CI
failures that also occur on main and are not part of this change. Restore
both files so this branch carries only the public-followup deliverable fix.

* no-mistakes(review): Refuse a repeated --deliverable key at emit argument parsing

* no-mistakes(document): Clarify public-followup validation and rejection-wake documentation
* Add verified Devin CLI worker adapter

* no-mistakes(review): Drop Devin resolver refusal and launch marker

* no-mistakes(review): Verify devin in bootstrap, fold kind rule, update docs

* no-mistakes(document): Document Devin sidecar, resume, and worker-only facts

* no-mistakes(document): Document Devin interrupt, liveness anchor, composer signals

* fix(control): never pair Devin interrupt presses on an idle agent

A fast double Escape on an idle Devin opens its /revert picker, where Enter
reverts file changes. fm-control now sends the second press only after the
first renders Devin's 'esc again to interrupt' armed hint, never sooner than
0.5 s, closes a revert picker a mistimed press opened with one Escape, and
refuses to type the exit command while that picker is open. An unarmed
interrupt reports cancel=not-running and leaves the busy record untouched.

* fix(devin): disable Claude hook import and commit attribution for workers

The per-task Devin config now forces read_config_from.claude=false, so a
worker no longer runs the user's or project's Claude Code hooks (including
Herdr's Claude agent-state hook), and attribution=false, so Devin adds no
Co-Authored-By trailer or Generated-with line to commits and PRs.

* test(devin): extend live guard and record Herdr and revert-picker evidence

The credentialed live guard now fails if an imported Claude Code hook runs,
if the worker's commit carries Devin attribution, if an idle interrupt sends
more than one press or opens the revert picker, or if an open picker lets
exit through or is closed with a revert. The Devin reference, agent-control
doc, and verification records carry the 2026-09-22 tmux and Herdr lab results,
including the Herdr exit refusal.

* no-mistakes(document): Correct Devin documentation links and lifecycle guidance

---------

Co-authored-by: Denis Beliaev <battler73@yandex.ru>
…id#5322)

fm-crew-state classifies the no-mistakes outcome 'passed-with-skips' as
unknown, so a finished worker awaiting merge is re-alerted as stale. The
same blind spot lets fm-teardown's pre-teardown terminal-run check refuse
a legitimate abort race that lands on this outcome.

Map passed-with-skips to done in crew-state resolution, keeping the
skipped publication/CI verification visible in the detail rather than
reporting a clean pass, and recognize it as terminal during teardown.
…nguid#5382)

* fix: refuse missing backend adapter before source

* no-mistakes(review): Gate backend precheck under stock Bash

* no-mistakes(document): Clarify adapter precheck docs

* no-mistakes(lint): Suppress intentional child Bash ShellCheck warning
…kunchenguid#5338)

* fix(test): repair tmux liveness and calm follow-up loaded_off regressions

Both self-tests fail on untouched main on a host whose coreutils are a
multicall binary and whose Chrome has no pre-warmed profile, and each failure
masks the other's file.

tests/fm-tmux-agent-liveness.test.sh - the stand-in harness processes were
symlinks to the host's `sleep`. A single-purpose `sleep` runs happily under
another name, but a multicall coreutils binary (uutils or busybox) resolves its
applet from argv[0]: `claude-link -> sleep` invoked under the harness name runs
the wrong applet and exits immediately, so no foreground process exists and
every positive case reads not-alive ("last verdict for liveness:agent was
missing (expected alive); title=sh comms=[sh ]"). Build a dedicated spinner as
the stand-in target, exactly the way the version-string case already builds its
executable, and require the fallback target to demonstrably survive the rename
before using it. Every assertion is untouched; the stand-in identity signal is
unchanged (the kernel still records the symlink name as the executable
identity).

tests/fm-calm-pi-extension.test.sh - render_export_dom pinned a brand-new
`--user-data-dir` per attempt. On Google Chrome for Testing 151.0.7922.34 that
pristine profile makes Chrome's first-run initialization never complete: the
browser and its renderers start, but --dump-dom never returns, so all three
bounded attempts end exit=0 timed_out=yes bytes=0 and the DOM assertions never
run ("could not render calm-mode HTML export DOM"). Chrome's own profile
creation under a fresh HOME renders the same document in about a second, so the
helper now gives Chrome a private per-attempt HOME instead of the explicit
profile flag. Each attempt still gets an isolated profile, and every DOM
assertion is unchanged.

Root-cause evidence: a pristine --user-data-dir with `--headless=new
--dump-dom` had not returned after 150s, while the same command with an empty
HOME and no --user-data-dir returned the full DOM in ~1s, and reusing an
already-populated profile also returned it in ~1s. The render failure masked
the rest of the file: with it repaired, the Pi follow-up loaded_off case passes
unmodified against an installed @earendil-works/pi-coding-agent package.

These two failures block downstream validation of every lane on hosts with
multicall coreutils or a fresh Chrome profile.

Verification:
- timeout 300 bash tests/fm-tmux-agent-liveness.test.sh -> exit 0, 16 assertions ok
- timeout 700 bash tests/fm-calm-pi-extension.test.sh -> exit 0, 13 assertions ok,
  including the Pi operational follow-up loaded_off case
- bash -n and shellcheck clean on both touched files
- rest of tests/: bin/fm-test-run.sh --all bounded by timeout 900 completed 17 files with 0 failures (fm-afk-contract.test.sh through fm-backend-herdr-launcher-workspace-e2e.test.sh), then the bound cut off the 18th (fm-backend-herdr-presentation-e2e.test.sh, a real-herdr-gated lab test) with no failure recorded

* fix(test): give wake-queue observation checkpoints the alerting ceiling

tests/fm-wake-queue.test.sh's secondmate stall case runs bounded foreground
watcher checkpoints whose job is to record an observation, with the alerting
checkpoint that follows asserting the stall. A checkpoint's exit publishes a
downtime marker, and the next checkpoint consumes it only by reaching the end of
the watcher's poll loop, where the recovery surfacing runs after the stall tick;
the observation itself is recorded by that same stall tick. On a loaded host a
1s ceiling sits under the cost of that iteration (which includes a pane capture
in the active-turn gate), so the observation was never recorded, the downtime
marker stayed pending, and the alerting checkpoint surfaced
`check: rearm-resurface` instead of the stall it asserts:

  not ok - a foreign queue with no progress did not alert: check: rearm-resurface
  not ok - a frozen reprovisioned queue generation was hidden: check: rearm-resurface

Give the observation checkpoints that feed a later alert the same 4s ceiling the
file already documents for alerting checkpoints. The ceiling is only a bound - a
checkpoint still returns on its first actionable wake - so no assertion is
weakened, and the quiet windows get longer, not shorter.

* no-mistakes(document): docs: correct export-DOM Chrome render root cause

* no-mistakes(review): Isolate Chrome profile on macOS, dedupe tmux CC_BIN lookup

* chore: re-trigger fork workflow approval for triage

---------

Co-authored-by: Captain <blackxwhite88@users.noreply.github.com>
Co-authored-by: kunchenguid <kunchenguid@users.noreply.github.com>
…chenguid#5383)

* fix(bin): classify a status span without re-folding the whole log

A watcher poll could take minutes, so its liveness beacon aged past the
guard's 300s grace and the Stop auto-arm reported the watcher down. On the
main home, cycles ended with beacon_age 91-235s while healthy and 534-706s
while the laptop was CPU-starved.

Cause: whenever a newly appended status span held a keyed needs-decision
or blocked line, status_span_first_actionable_record re-read and re-folded
the ENTIRE log to decide whether that opening was still live, forking
several subshells per line. On a remote second mate's mirrored parent
channel (1.2MB, ~2300 lines) that is 13-20k subshells, about 17s per log
per classification when idle, paid by every signal and heartbeat scan.

Nothing regressed recently: subshell counts per classification were
20,272 from kunchenguid#3268 (2026-08-29, which introduced the whole-log fold) and
13,188 from kunchenguid#3753 onward through HEAD. The cost grew with log size, since
parent-channel logs only grow.

Fix: fold only the captured span. An accepted opening does not depend on
earlier lines and only later lines close or supersede it, and every later
line lies inside the span, so the span fold names the same live openings
at a cost bounded by the span. Old and new classification outputs are
byte-identical across 51 span offsets of real-shaped secondmate and ship
logs.

A real-watcher regression test records every read the classification
makes through the span-reader seam and asserts none reaches before the
classified offset; it fails on the old code (5,157 bytes read from
offset 0 to classify an 84-byte span).

* no-mistakes(document): Clarify span classification and watcher regression coverage
kunchenguid#5362 and kunchenguid#4878) (kunchenguid#5381)

* test: fix watcher timing flakes in fm-pr-check-security

The bounded watcher's hang guard now counts only the watcher's own time: a
case marks the intervals where it holds the watcher on injected work or makes
it wait on concurrent work, and those no longer count against its budget. The
budget itself stays at main's sixty seconds. The helper also stops forcing a
one-second per-check timeout, which killed a correct merged poll whenever that
poll took longer than a second, so the watcher only retried it or exited on a
later check's wake without the merge.

The concurrent-publication case pauses the guard while its arming is in
flight, and its task now sorts before the contributions observer the arming
also registers, so the watcher stops on the poll under test before running
that unrelated fleet snapshot. The case also prints the watcher's stderr when
it fails.

The replacement case pauses the guard while the re-arm runs inside the
watcher, runs that injected arming with the fixture root every other arming
here uses, and waits on the replacement merge's process instead of a
two-second cap. Merged-poll runs retire the contributions observer before the
watcher starts, since no case here exercises it.

The returned-descendant case no longer races a four-second sleep or a TERM
landing at an arbitrary point in the watcher's idle loop: its descendant holds
until killed, and a second check in the same cycle witnesses that it was
drained and stops the watcher.

* no-mistakes(ci): Reproduced the intermittent board-render failure. Its Lavish stub listed an open session but omitted the session-state record required by the listener, so the build could race the listener’s exit. Added matching fixture state; the affected suite passed three consecutive runs, and shell syntax and diff checks passed

* Revert "no-mistakes(ci): Reproduced the intermittent board-render failure. Its Lavish stub listed an open session but omitted the session-state record required by the listener, so the build could race the listener’s exit. Added matching fixture state; the affected suite passed three consecutive runs, and shell syntax and diff checks passed"

This reverts commit 6a59859.
…enguid#5385)

* feat: record task.pr_ready in the fleet ledger when a task PR is registered

* feat: record worker status lines in the fleet ledger as they are written

* no-mistakes(review): Keep worker status append failures and pass the resolved config to the ledger

* no-mistakes(review): Resolve relative config override before embedding in worker command

* no-mistakes(document): Clarify fleet ledger status capture timing
…unchenguid#5386)

* test: synchronize foreign secondmate stall legs on the watcher's recorded observation

Each leg of test_secondmate_foreign_queue_stall_tracks_progress_and_alerts_once
ran the watcher under a 1s or 4s wall-clock checkpoint, but every later leg
depends on the progress observation the previous leg's watcher recorded. Under
load the watcher was killed before its first stall tick, the observation was
never written, and the next leg treated its own sighting as the first one, so
the stall alert never fired.

Run the watcher directly and end each leg on its observable outcome: the
progress marker recording the expected observation, or the watcher's own first
wake. Also move a comment orphaned above this test back to the drain liveness
test it describes.

* no-mistakes(review): Wait for full stall reset before stopping watcher leg
kunchenguid#5391)

The listener resolves its server from that store before it polls. Without a session for this board, it exits in the gap after the build has already sampled a live claim.
…#5390)

* fix(bin): prune a torn-down task's wake rows at teardown

Prune pending durable wake rows (.wake-queue) for a task when it is torn
down, clearing stale wakes for its target window, signal wakes for its status
or turn-ended files, and task-specific check wakes.

Fixes kunchenguid#3419.
Adjacent to kunchenguid#5252.

- bin/fm-wake-lib.sh: add fm_wake_queue_prune_task
- bin/fm-teardown.sh: call fm_wake_queue_prune_task in cleanup_firstmate_home_children and main teardown
- tests/fm-wake-queue.test.sh: add test_wake_queue_prune_task

* no-mistakes(document): docs: note teardown prunes a task's wake rows

---------

Co-authored-by: Captain <blackxwhite88@users.noreply.github.com>
…ixture readiness (kunchenguid#5392)

* Make portable tests match resolved host paths

Summary:
- Match macOS full Node command paths by basename in the Gemini behavior test.
- Mirror symlink-resolved Nix PATH behavior and give the loaded-host race bounded headroom.

Testing:
- bin/fm-lint.sh
- bin/fm-test-run.sh tests/fm-on.test.sh tests/fm-gemini-harness.test.sh tests/fm-procevent.test.sh

Related:
- None

* no-mistakes(review): Mirror production PATH helper rules per directory group in tests

* no-mistakes(test): Wait for orphan runner start marker instead of fixed sleep

* no-mistakes(document): Clarify gemini ancestry test comment for versioned node comm

---------

Co-authored-by: Sandeep Salwan <salwansa@amazon.com>
…ns (kunchenguid#5389)

The sibling secondmate stall cases in tests/fm-wake-queue.test.sh now wait for the watcher's recorded observation instead of a one-second wall-clock checkpoint, so they can neither fail nor pass vacuously under load. Deterministic proof with a 5s watcher launch delay: before the fix 4 cases passed vacuously and 6 failed; after it all 10 pass on the recorded observation.

Also includes a CI flake fix from validation: fm_control_harness_supported in bin/fm-control-lib.sh finishes reading the harness allowlist before returning, removing intermittent broken-pipe diagnostics. Behavior is unchanged.
… tail (kunchenguid#5336)

* fix(bin): refuse a Herdr submit that would send only a message tail

A long typed payload can sit in the composer as a suffix, or as a paste placeholder plus a remainder, and the following Enter was still reported as delivered. Prove the selected composer holds the payload before Enter, and report failure when it does not.

* no-mistakes(review): Scope Herdr payload proof to Claude, clear composer on refusal

* no-mistakes(test): Clear refused Herdr composer drafts one wrapped row per press

* no-mistakes(test): Accept Claude's multi-line paste placeholder in Herdr submit proof

* no-mistakes(review): Accept Claude read-back that drops U+2063 in Herdr proof

* no-mistakes(document): Document Herdr proof ignoring U+2063 operational mark

* no-mistakes(ci): I made a one-line test change. The failing check comes from a timing race in an existing test that this PR doesn't touch. **What failed:** `tests/fm-procevent.test.sh` failed at "the superseded paced runner invoked its stale command" (line ~3313). The PR only changes the Herdr files and their tests, and the same shard passed on main at the base commit. **Why it can fail:** the fixture starts a second runner with a 3-second launch floor (the minimum wait since the source's last launch). That runner sleeps for the rest of the floor and only then checks whether its registration was replaced (`fm_procevent_launch_floor_wait` in `bin/fm-procevent-lib.sh`). The test then waits for the claim and re-registers the source. If that takes longer than about 3 seconds after the first launch, the old runner wakes up, finds its registration still current, and runs the stale command. That produces the second log line the test reports. The CI shard was slow (this one test took 160 s). **Fix:** in `tests/fm-procevent.test.sh` I raised the superseded runner's floor from 3 to 15 seconds and added a comment explaining why. The floor now outlasts the fixture setup even on a loaded runner. Nothing else changed: the first launch and the later fresh-registration start still use a 3-second floor, and no product code changed. **Verification:** - The full test file can't give a reliable result on this machine (load average about 64 on 8 cores). It failed earlier, at the reconcile assertion around line 1680, before it reached this section. - I ran the changed section by itself (file setup plus the pacing-race block) five times with the fix: all passed, in about 9-13 s each. - The original code also passed five out of five, so the race didn't reproduce locally. The diagnosis rests on the code path and the CI log. - I haven't seen the full file or the CI shard pass with the fix yet

* no-mistakes(test): Accept Claude folder-trust prompt via down+enter in live e2e

* no-mistakes(document): Note unreadable Claude composer refusal in Herdr docs
…unchenguid#5427)

Speaking as Kun's firstmate: squash-merging — opt-in (forge=gerrit registry-gated; default project-mode stdout restored to two words), attestation MATCH, CI+NM green, safe review, MERGEABLE.
…uid#5358)

* feat(bin): add an opt-in per-home worker account pin

A home that mixes work and personal accounts for one runner had no way to
say which account its workers launch on: Claude workers inherited whatever
CLAUDE_CONFIG_DIR the supervising process had, Pi workers the pane's ambient
root, and an ambient API key outranked both, with no signal at launch.

config/claude-account and config/pi-account now pin that choice per home.
With neither file every launch is unchanged. With one, every launch of that
runner from the home (ship, scout, local secondmate, raw Claude command, and
relaunch) runs under the declared root, and the spawn refuses before any
endpoint exists when the file is malformed or the runner's own check
(claude auth status, pi auth check with a model-listing fallback) says the
pinned account is not signed in. The check runs in a cleared environment so
an ambient credential cannot answer for an empty root. A pinned Claude launch
sheds the environment credentials Claude ranks above a stored login; a pinned
Pi launch needs an explicit <provider>/<id> model for a declared provider and
also carries --provider. The chosen account is printed on the spawned line and
recorded in the task record, and relaunch checks the pin before stopping the
running agent.

* test(secondmate): give the concurrent config-push wait room for a slow host

test_config_reread_serializes_concurrent_pushes waited about two seconds for
the first fm-config-push.sh to reach its first send-keys. On a slower host
that push takes four to five seconds, so the test failed on main before the
push ever got there. The loop still leaves as soon as the marker appears, so
the larger bound costs nothing where the push is fast.

* no-mistakes(review): Refuse raw Claude account overrides under a pin
…ts (kunchenguid#5470)

* fix(bin): keep the Herdr lab session option before a -- delimiter

fm-herdr-lab.sh run appended --session <lab> after every argument, so a
command with a passthrough delimiter such as agent start ... -- <agent args>
handed the session flag to the agent and Herdr routed the call by the
caller's ambient socket instead of the lab.
The helper now inserts --session <lab> immediately before the first --
delimiter and keeps the trailing form otherwise.

* no-mistakes(document): Clarify Herdr lab session option placement
* feat(bin): guard the partition, harness pin, and bounded exec for a non-Pi supervision host

Lease liveness is now the pure record test in every calling context, so an
unmarked main honors a live branch lease held by a separate process, and a
lease file engages the guard's claim serialization for any caller; a home
with no lease files still takes no lock.

bin/fm-harness.sh honors FM_SUPERVISION_PRIMARY_HARNESS while
FM_SUPERVISION_ACTOR=branch, so a supervision branch running under another
harness resolves own, crew, and secondmate to the primary's harness.

fm_tasks_axi's watchdog moves into bin/fm-timeout-lib.sh as fm_exec_timed with
a separate grace: the perl watchdog is preferred, runs the command in its own
process group against wall-clock deadlines, forwards TERM/INT/HUP, and reaps
the group, so a descendant holding captured output can no longer keep the
caller waiting past the bound on a host without timeout.

The Claude Stop auto-arm header records that Claude drops the exit 2 of a hook
it terminated at the configured timeout, re-measured on Claude Code 2.1.281.

* fix(bin): state that fm_exec_timed cannot reach a descendant in its own process group

Live runs of real Claude and Pi engine turns under the bound showed both CLIs
start every tool command in a process group of its own, so those processes end
through the engine's own TERM handling rather than the group signal or reap.
Also clears the new timeout test's ShellCheck findings.

* no-mistakes(document): Clarify cross-harness lease documentation
…henguid#2648)

* feat(bin): make the ship-branch prefix configurable per project

fm-brief.sh hardcoded every generated ship branch to fm/<task-id>, which
leaks that firstmate produced the branch/PR - unwanted for a third-party
public repo that does not use this tooling.

Add an optional --branch-prefix flag to fm-brief.sh (default "fm/", so
existing installs are unaffected) and teach fm-project-mode.sh - the
registry's single-owner parser - to resolve a project's optional
"branch=<prefix>" data/projects.md annotation via a new --branch-prefix
query, order-independent with the existing mode/+yolo tokens. Firstmate
resolves the override at task intake and passes it explicitly, mirroring
how --mode already works; fm-brief.sh itself never reads the registry.

An empty override resolves to a bare "<task-id>" branch rather than a
leading slash. All five previously hardcoded fm/$ID sites (branch
creation, never-push rule text, definition-of-done text, and the status
message) now render the resolved prefix consistently.

* no-mistakes(review): Wire branch-prefix intake in AGENTS.md; fix fm-merge-local.sh hardcoded fm/ prefix

* no-mistakes(document): docs: document configurable ship-branch prefix in architecture.md

* no-mistakes(review): Persist immutable branch contracts

* no-mistakes(document): Document configurable ship branch prefixes

* no-mistakes(lint): Captain: fix ShellCheck test warnings

* fix(bin): map bearings PR rows to their recorded ship branch (kunchenguid#1887)

fm-bearings-snapshot.sh keyed a PR back to its task by string-matching
the headRefName against the fm/ prefix, so any project whose branch
prefix was overridden (e.g. via kunchenguid#2648's branch=<prefix> registry
annotation) had its PRs silently drop to task "-" in the bearings
view, exactly the third fm/-assumption issue kunchenguid#1887 named alongside
fm-merge-local.sh and fm-bearings-snapshot.sh itself.

fm-fleet-snapshot.sh now surfaces each task's recorded branch=
metadata field in its JSON task rows, and fm-bearings-snapshot.sh
cross-references a PR's headRefName against those recorded branches
before falling back to the legacy fm/ prefix heuristic, so a custom
branch prefix maps a PR back to its real task.

Adds a regression test proving a PR opened against a fix/<task-id>
branch resolves to that task instead of "-"; confirmed it fails on
the prior startswith("fm/") logic and passes with this change.
ShellCheck clean; full fm-bearings-snapshot.test.sh and
fm-fleet-snapshot-view.test.sh suites pass.

* fix(ci): align lint arithmetic-looking assignment and stale Bearings snapshot count

- Quote the --branch-prefix want_value assignment in fm-brief.sh, fm-promote.sh,
  and fm-spawn.sh so ShellCheck SC2100 no longer misreads the plain string
  'branch-prefix' as arithmetic shorthand.
- Bump the Stock macOS Bash snapshot job's hardcoded Bearings test-count
  assertion from 59 to 60: this PR added a Bearings test, so the count was
  stale, not the feature.

* no-mistakes(review): fix(bin): honor recorded ship branch in relaunch and review-diff

* no-mistakes(document): docs: complete branch-prefix flag in brief and promote headers

* fix(lint): quote branch-prefix parser token; drop unused BRANCH_Q after rebase

* no-mistakes(review): Restore %q branch escaping in promotion instructions with regression test

* no-mistakes(document): document recorded ship branch and prefix flag

fm-review-diff.sh's header is the owner of its branch-resolution
contract; it still described only the legacy local-branch behavior
after the change made review-diff honor state/<id>.meta's recorded
ship branch. README's feature bullet enumerates the registry's
optional flags and was missing the new branch=<prefix> override.

* no-mistakes(lint): Silence SC2016 on intentional single-quoted sed expression

* no-mistakes(review): address branch-prefix review findings in DoD and project-mode

* no-mistakes(test): branch-prefix suites pass under tasks-axi 0.2.6; environment-only failure

* no-mistakes(document): purge stale fm/ branch naming from docs and headers

* fix(test): assert the merged epoch status wording in the branch-prefix override test

The rebase resolution of tests/fm-brief.test.sh kept the branch's
pre-merge \`done: ready in branch ...\` assertion while the merged
fm-dod-lib.sh (carrying main's epoch-stamped status line) renders
\`done [at=<epoch>]: ready in branch ...\`. Align the assertion so the
override-consistency test matches the behavior it verifies.

* no-mistakes(review): Address remaining branch-prefix findings in four bin scripts

* no-mistakes(test): skip real-tasks-axi tests below the repo's 0.2.6 floor

* no-mistakes(document): document spawn's branch-prefix registry deviation notice
mremond and others added 29 commits September 25, 2026 11:22
…5534)

* fix(bin): refuse a merge when a required check never reported

fm-pr-merge.sh built its GitHub refusals only from checks present in
statusCheckRollup, so a required check that never ran was simply absent and
the merge proceeded on the subset that reported, contradicting its own
"every required check green" claim.

The GitHub verify now reads the base branch's required contexts from the
forge itself - the classic branch protection summary on
GET repos/{o}/{r}/branches/{b} and the active ruleset rules on
GET repos/{o}/{r}/rules/branches/{b} - and refuses when a required context
has no entry in the same rollup, at the same head, that the merge is bound
to. Absence reads as unknown, never green. The required-set read joins the
existing refusal list, so a draft, a red check, and an unreported required
check are all reported together.

Could not read vs nothing required: both endpoints need only repository
read access. The admin-only GET .../branches/{b}/protection endpoint is
deliberately not used: it answers a non-admin token with the same 404 an
unprotected branch gets (observed live on kunchenguid/firstmate main with
this token), which would read a missing permission as "nothing required".
Any failed or malformed read of either source (auth, missing fine-grained
permission, rate limit, network, 404, unexpected shape) refuses the merge
with a line naming the unreadable source. The one exception is GitHub's
plan-gated 403 on the rules endpoint ("Upgrade to GitHub Pro or make this
repository public"), which already means "this repository has no branch
rules" for the merge-queue reader; that check moves into one shared helper
and the classic summary still decides for such a repository.

Attended waiver: --allow-missing <check-name> is the twin of --allow-red and
follows the same design and recording path: once, separate name argument,
waives only that exact unreported required check, still requires every
other required check reported and every check green, never waives an
unreadable required set, refused while the away-posture record exists, and
refused on GitLab. Merge-state BLOCKED policy is unchanged.

How this differs from the withdrawn kunchenguid#5353 (read from its diff):
- kunchenguid#5353 read the admin-only branches/{b}/protection endpoint and treated
  its 404 as "no required checks", so for any non-admin token the required
  set silently read as empty; this change reads the read-access branch
  summary and treats every failure as unreadable.
- kunchenguid#5353 ignored rulesets; this change also reads required_status_checks
  rules from the effective branch rules.
- kunchenguid#5353 made separate per-head REST reads of statuses and check-runs capped
  at per_page=100 with no pagination; this change checks presence in the
  same statusCheckRollup view the red-check gate already reads at the
  verified head.
- kunchenguid#5353 stopped at the first unreadable read; this change reports it as one
  refusal among all the others.
- kunchenguid#5353 also claimed kunchenguid#5345 (lock stealing) and changed 39 files, most
  unrelated; this change is kunchenguid#5344 only.

Live proof, read-only (a gh wrapper refused every merge and mutating call):
- cli/cli#14474 (trunk requires 3 classic build contexts, none ran):
  refused, naming build (macos-latest), build (ubuntu-latest),
  build (windows-latest); with --allow-missing "build (macos-latest)" it
  still refused, naming the other two.
- cli/cli#13665 (ran build (ubuntu-24.04-firewall) instead): refused,
  naming build (ubuntu-latest).
- hashicorp/terraform#39262 (ruleset-required checks absent): refused,
  naming Code Consistency Checks, End-to-end Tests, Race Tests, Unit Tests.
- cli/cli#14485 (all required reported and green): verified; the wrapper
  blocked the merge call and the pull request read back open.

Fixes kunchenguid#5344

* fix(review): Preserve required-check producers and aggregate independent read failures

* fix(document): Clarify required-check verification and waiver documentation

* fix(bin): match an app-bound required commit status by name

The producer-identity check resolved an app-bound required context only
against check runs, so a required context that the required app reports as
a commit status could never match and always read as "has not reported".
A commit status carries no app id to compare, so an app-bound requirement
that arrives as a status now matches by name, as before producer binding;
check runs keep requiring the configured producer app.

Live, read-only: hashicorp/terraform#39262 requires license/cla from
integration 865473, reported green as a commit status by the CLA app. The
previous head refused it as unreported; this head no longer does, while
still naming the four required check runs that never ran there.

Refs kunchenguid#5344

* fix(document): Clarify accepted commit-status producer verification limitation

---------

Co-authored-by: firstmate-oss <firstmate-oss@kunchenguid.local>
…uid#5696)

* fix(bin): never offer a persistent secondmate for teardown

The return brief's "Landed, cleanup due" scan listed every state/*.meta
record carrying a pr= and a merge-notified marker without regard to kind, so
a secondmate record holding a relayed child's merged PR put the mate itself
up for "bin/fm-teardown.sh <mate>" cleanup. A secondmate is a persistent
worker, never landed work.

- bin/fm-afk-return.sh: skip kind=secondmate in the landed-cleanup scan.
- bin/fm-pr-check.sh: refuse to record pr= or arm a merge watch on a
  kind=secondmate record before any side effect; a PR reported on its routed
  status channel belongs to a task in the mate's own home, which arms its
  own watch.
- bin/fm-watch.sh: a merged result from a poll already armed on a secondmate
  retires the poll silently - no merge outcome, marker, or wake.

* no-mistakes(document): Document secondmate merge-watch and return-brief exclusions

* no-mistakes(ci): CI failed in an unchanged watcher-shutdown test whose three-second wait was sensitive to runner load. Increased the wait for both state- and home-deletion cases without changing watcher behavior. The full fm-watch-arm suite passed locally; syntax and diff checks passed
…unchenguid#5702)

* fix(bin): refuse unknown dash-leading args in public-posting fm-x scripts

fm-x-reply.sh collected any unrecognized argument into the positional
pool and took the first one as the reply text, so an invocation like
"fm-x-reply.sh <id> --followup --final <text>" posted the literal
string "--final" to X and silently dropped the real text.

Make argument parsing strict in every script that can post publicly:
an unknown dash-leading argument, a dash-leading request_id/task id, a
dash-leading option value, or a surplus positional now exits 2 with a
usage error before any config load, outbox write, or network call. Reply
text starting with '-' is still accepted via --text-file or stdin, and
--help is honored wherever it appears instead of becoming text (a --help
forwarded through fm-x-followup.sh would have counted as a posted
follow-up and mutated the link).

fm-x-link.sh and the fm-public-followup scripts already refuse unknown
arguments; fm-x-poll.sh takes none.

* no-mistakes(review): Refuse surplus follow-up text sources; drop post-ID help branches

* no-mistakes(document): Clarify reply and follow-up argument usage

* no-mistakes(review): Refuse dash-leading --text-file operands in fm-x-reply

* no-mistakes(document): Correct follow-up argument parsing comment

* no-mistakes(document): Document dismiss argument rejection in script header
…nchenguid#5701)

* feat(bin): latch the supervision host after repeated engine errors

Rung 3c-1 of the PR 5631 re-cut: the host copies the Pi branch's
broken-session policy. Two consecutive engine errors latch the session;
every away wake then reaches main with one supervision-host line for a
five-minute cooldown, after which one wake probes the engine, and each
failed probe doubles the cooldown up to one hour. A reported turn without
an engine error clears it. The latch is kept per main session, engine, and
model in state/.supervision-host-health, and the engine conversation now
uses the same main-session key, which includes the lock holder's process
identity so a recycled pid never shares either.

Lifted from the validated 5631 tree and adapted to main's away-only host:
the attended recovery line and attended cooldown pass-through are left for
the attended core, so a recovery is only logged.

* no-mistakes(document): Consolidate supervision-host latch documentation
…kunchenguid#5535)

* fix(bin): absorb routine second-mate progress while surfacing routed replies

Fixes kunchenguid#2959

A kind=secondmate task's status signal was never absorbable, so a healthy
mate's routine working: and paused: appends woke the primary every time.
signal_crew_provably_working now reads the mate's lines new since the
watcher's classified position: a decision, blocker, terminal outcome, note:,
correlation-marked line, or unknown verb still surfaces regardless of busy
evidence, while unmarked working:, paused:, and resolved: fall through to the
same provably-working absorb an ordinary crewmate gets.

* no-mistakes(review): narrow secondmate routine absorb to working and paused
* fix(bin): use gh-axi for the ship DoD draft check

* no-mistakes(review): use PR number not URL in gh-axi draft check
… only (kunchenguid#5520)

* fix(bin): drop status prose from the inactive-outcome dedupe identity

The inactive-outcome receipt fingerprint included the child's sanitized
last status line, so a persistent child appending routine prose after one
terminal outcome minted a fresh parent event per sentence. Bind the
identity to incarnation, task id, terminal state, and PR only, keeping
the last line in the record as status_head evidence.

Fixes kunchenguid#2960

* no-mistakes(document): note structured-only inactive receipt identity in regression coverage
…unchenguid#5707)

* feat(bin): record the supervision host's dialog mirror on Claude and Cursor

Add bin/fm-host-mirror.sh, the one owner of the supervision host's dialog
mirror file, cursor, lock, and feed, plus the main-session key it keys
entries to. The tracked Claude UserPromptSubmit and Stop hooks and the
Cursor beforeSubmitPrompt and afterAgentResponse hooks record the captain's
prompt and main's reply, only on a home with config/supervision-host, from
a genuine primary checkout, for the lock-owning session. The mirror lands
inert: writers record and nothing reads it yet; attended supervision on the
host is the later step that consumes the feed.

Codex, Grok, OpenCode, and omp have no writer here.

* no-mistakes(review): Scope mirror dedup to session, atomic appends, marker-inclusive caps

* no-mistakes(document): Clarify dialog mirror scope and remove duplicate contract details

* no-mistakes(document): Correct Cursor hook documentation for dialog mirror registration

* no-mistakes(review): Pass mirrored dialog text to jq via stdin

* no-mistakes(document): Clarify dialog mirror documentation and remove duplicate claims

* no-mistakes(review): Preserve internal dialog whitespace; drop mirror check and verified modes

* no-mistakes(review): Drop only identical mirror repeats; remove redundant chmod guard
… escalations are not repeated (kunchenguid#5731)

* fix(bin): retire check-row receipts on branch acks and report an unchanged situation once

* fix(bin): scope a branch acknowledgement's check-row receipt retirement to
  its granted sequences

The away posture lifts the attended partition's check/decision exclusions, so
a branch grant can name check-kind rows - but the branch-actor ack still
assumed check rows were main-only and skipped every receipt scan. The queue
row was consumed while its terminal-outcome .pending receipt stayed behind,
and each inactive-reconcile cadence scan re-queued the same fingerprint. In
the first real away window on the supervision host that re-escalated one
unchanged held-PR situation on every cycle (~1,734 of 4,149 outcomes).

A branch ack now scans inactive-outcome and inactive-reconcile receipts and
commits secondmate stall receipts against exactly the sequences in its
eligible-row snapshot - the same rows it consumes - instead of none. Attended
grants still name no check row, so the scans find nothing.

* fix(bin): store a repeated captain verdict as routine while the task's
  durable situation is provably unchanged

fm-branch-outcome.sh append computes a mechanical situation key per captain
row - metadata bytes, captured status-log endpoint and identity, live
crew-state verb, worktree head - and anchors it in
state/.<task>.branch-captain-key. A later captain verdict whose recomputed key
matches is stored as routine with "unchanged since seq <N>:" prefixed to its
summary, so one situation escalates once until something provably changes. A
task with no readable status ledger is never demoted, an unreadable record
fails toward reporting, and teardown removes the sidecar with the task's
other branch records. The append-only store schema is unchanged.

This covers both hosts: the Pi supervision branch and the supervision host
both funnel reports through append.

* docs: check rows are main-owned only while attended; the away posture grants
  them to the branch, whose ack retires their receipts exactly

* test: the away-flood reproduction as a regression test (branch ack retires
  the receipt and later scans stay quiet), store-level dedupe coverage, and a
  branch-ack secondmate stall receipt case

* fix(bin): restore the secondmate child devin-config cleanup path

The branch-captain-key sidecar addition mistyped the sibling entry as
.$child_id.devin-config.json, so a forced secondmate teardown would have
stopped removing each child's real <id>.devin-config.json. Restore the
original path and add a behavioral test that stops the child sweep mid-loop
on a refused close, proving the cleaned child's devin config and captain
anchor are both removed while the unconsumed child's records are retained.

* no-mistakes(review): Key captain dedupe on the covered wake rows' fingerprint

* no-mistakes(review): Drop captain-key demotion; prove one escalation on both surfaces

* no-mistakes(review): Drop unrelated teardown test; cite both receipt test files

* no-mistakes(document): Docs already match branch-ack check-receipt retirement
…oorbell (kunchenguid#5664)

* fix(calm): deliver Claude-bound operational input as a record-backed doorbell

Claude Code 2.1.280 removes U+2063 from every submitted prompt, so a typed
operational envelope reaches a Claude Code primary as plain text. The away
daemon now writes the envelope to a record under state/operational-inbox and
types only a plain doorbell naming it; the /afk return check and the Calm mod
recognize the doorbell only when that record holds a current envelope. Marker-
preserving harnesses keep the typed envelope. The live Calm guard accepts the
2.1.280 module-load log line, drives the doorbell, and asserts thinking stays
hidden.

* no-mistakes(review): Fix operational record retention at 7 days and document prune limit

* no-mistakes(document): Point Calm bounds at 2.1.280 evidence; fix afk-exit comment

* no-mistakes(lint): Pick newest Calm e2e transcript without parsing ls

* docs(calm): add a minimal turning-Calm-on step for Claude Code

* fix(spawn): deliver the Claude launch brief as a record-backed doorbell

Claude Code strips U+2063 from the launch-prompt argument too, so a
worker's launch brief arrived with its operational marker removed.
Publish the brief as a record in the receiving home's operational
inbox - a secondmate's own state, not the primary's - and pass only
the printable doorbell naming it, falling back to the typed envelope
when the record cannot be published so the brief body still delivers.

Unwrap doorbell-carried digests in the daemon digest tests that still
read the raw send log under the claude pin, and update the documented
bounds now that launch briefs hide like the other operational rows.

* test(spawn): cover a secondmate's launch-brief record landing in its own home

The record-backed doorbell resolves its state through the receiving
pane's home, so prove a claude secondmate launch publishes into the
seeded secondmate's operational inbox and never leaks a record into
the primary's.

* no-mistakes(review): Pass primary harness to daemon, tighten retention, refresh verdicts

* no-mistakes(review): Prune operational records by exact seven-day elapsed age

* no-mistakes(review): Batch record pruning so large inboxes still expire

* no-mistakes(review): Refuse Claude spawn when brief record cannot publish

* no-mistakes(review): Drop thinking probe from Claude Calm live test and docs

* no-mistakes(review): Record dated Claude Code 2.1.282 reproduction evidence

* no-mistakes(document): Clarify operational doorbell documentation and record expiry

* no-mistakes(document): Correct AFK escalation carrier guidance

* no-mistakes(review): Describe operational record retention as about seven days

* no-mistakes(document): Clarify Calm delivery and operational record retention

* no-mistakes(review): Remove out-of-scope Calm launch guide from Claude docs

* no-mistakes(document): Document Claude launch-brief delivery and refusal

* no-mistakes(document): Correct stale operational-input documentation

* no-mistakes(ci): Fixed the stale Claude trust test to verify that worker and secondmate launches deliver readable, record-backed briefs instead of expecting brief paths in their commands. Annotated the daemon’s output variable for ShellCheck without changing behavior. The affected tests, daemon tests, ShellCheck, and diff check pass locally

* no-mistakes(ci): parse rebased Claude launch after trailer hook prefix

* no-mistakes(review): Trust launch-brief record and restore thinking bound doc

* no-mistakes(review): Parse final Claude launch statement; drop Stop-hook docs

---------

Co-authored-by: Mike Sewell <maikunari@protonmail.com>
Co-authored-by: no-mistakes <no-mistakes@localhost>
kunchenguid#5583)

A host-local relaunch rewrote only the far endpoint, so this home kept the old harness, model, and effort, and appending those keys after pr= broke pull-request poll authentication.
…ound (kunchenguid#5516)

tests/fm-watch-triage.test.sh finishes in about 434s alone and about 698s
under CI load, so the 900s bound the changed-suite runner applies produced
a false timeout under ordinary concurrent validation. Raise the automatic
bound to 1500s, which keeps every measured script under it while staying
below the 30-minute normal CI tier so a genuinely hung script still fails
here with its output before the job cap cancels the lane.

Fixes kunchenguid#3869
Refs kunchenguid#3565
…kunchenguid#5728)

* Fix nested watcher lock reclaim

* no-mistakes(review): Elect a single steal-mutex reaper and bound arm TERM wait

* no-mistakes(review): Reclaim self-held steal mutex and unify autoarm steal reaping

* no-mistakes(review): Resume own interrupted steal reap from its tombstone
…nguid#5710)

* test: hold the back-to-back boundary close on the host's own clock

test_park_boundary_holds_under_back_to_back_closes assumed two engine
turns fit in the ~16s pre-refusal window and that the stub finished a
turn in 3s. Under load the stub's real drain, report, and
acknowledgement take ~13s, so the turn either died at its bound (which
hands the wake to main, no boundary line) or the second close landed
past the window and the fixture failed while the boundary held. 3
failures in 5 runs at a load average near 11.

Hold the first turn on a release file instead: once the engine is in
flight, a second close is appended mid-turn and the turn is released as
the refusal window opens (park bound minus turn bound and grace, read
off the host's own start record). The queued close can then only wait
for the boundary on any machine speed, which is what the test asserts:
the boundary line ends the output, the demo.status row stays queued for
main, and no second engine turn ever starts. A host too loaded to start
the turn at all hands the first close to the same boundary exit.

After: 12/12 at load ~15-42.

* no-mistakes(review): Print boundary test deadline as a decimal integer

* no-mistakes(review): Hold boundary test turn on a FIFO, require full sequence

* no-mistakes(review): Remove stray before/after supervision-host test copies

* test: hold the late close's render until the refusal window opens

The boundary recheck test's node shim slept a fixed 10s, which assumed
the first close was read before the host's refusal window opened. Under
load the close arrived after the refusal check, so the host correctly
refused it before the successor started and the render snapshot never
appeared. Block the wake-prompt render on a FIFO released at the
refusal-open instant read from the host's own start record, so the
pre-turn recheck must refuse on any machine speed.

* no-mistakes(review): Derive minimal park bounds and refresh supervision-host shard hint

* no-mistakes(review): Drive park-boundary tests from a seam-gated host test clock
* fix(bin): stage remote home clones before publishing them

A remote home provision cloned the code root directly into the public
FM_HOME path while rollback() claimed rm -rf of that same path on any
failure. Bash defers trapped signals past a foreground child, but any
other cleanup or lifecycle path that removes the home directory races
the live clone's object copy, producing the CI flake "fatal: failed to
copy file to .../.git/objects/...: No such file or directory".

Clone into a private staging directory beside the home and publish with
an atomic rename once complete, so no cleanup can remove a directory a
live clone is still writing; a home that appears mid-provision now dies
cleanly instead of inheriting torn state. The regression coverage holds
a real clone mid-copy, removes the public path, and requires the
provision to finish and publish intact.

* no-mistakes(review): Prove home ownership by sentinel and hold only a live clone

* no-mistakes(review): Assert raced provision publishes a complete, intact clone

* no-mistakes(document): Document remote home staging and publication safety

* no-mistakes(lint): Fix ShellCheck warning in clone integrity assertion

* no-mistakes(document): Clarify remote home publication and rollback guarantees
* fix(control): keep a relaunched Pi worker's herdr pane status authority alive

Defect: after `bin/fm-control.sh <id> relaunch` (observed live on a herdr
Pi crewmate whose pane read idle while it ran its validation pipeline),
the pane froze at whatever its previous agent had last reported.

Cause, measured on herdr 0.9.1 against a real Pi: a pane has one status
authority, and for Pi with its integration installed that authority is
the lifecycle hooks, so herdr also skips screen detection for the pane.
In the crew shape the registration outlives its agent process (upstream
issue kunchenguid#4115; docs/herdr-backend.md "Restart and liveness behavior"), and
herdr applies only reports carrying the session identity it bound. A
replacement started fresh in that pane reports a NEW session, so its
state reports are ignored and the pane stays frozen. Nothing from
outside repairs it: `pane report-agent-session` and `pane report-agent`
for `herdr:pi` are accepted (rc=0) without being applied unless the
reporter is the registered pane agent, and `pane release-agent` on the
stale record changes nothing.

Fix: a relaunch preserves the binding instead of fighting it. The launch
owner reads the session reference the endpoint's own runtime recorded
(`fm_backend_herdr_pane_agent_session_ref`) and passes it back as Pi's
own `--session <path-or-id>` (`relaunch_resume_args`;
`fm_control_relaunch_resume_flag` owns which adapters and which
registered-agent labels qualify). That is the same reference herdr
itself resumes Pi panes with after a server restart, and the resumed
session's reports land again, which the live check confirmed: the pane
returned to working while the replacement worked and idle when it
settled, on the same session identity.

Safety: relaunch-only (a fresh spawn binds nothing), herdr-only (the one
adapter that records a per-pane session), Pi-family only, and only when
the registration's own agent label matches - so no other adapter's
conversation can be handed to a Pi launch. An unreadable, missing, or
malformed reference degrades to exactly the fresh-session launch that
existed before. No lifecycle, liveness, isolation, or merge guard is
touched, and an empty result leaves every non-Pi launch byte-identical.
`resume` remains a refused verb; docs/agent-control.md and the
harness-adapters references are corrected where they claimed Pi had no
verified resume form at all.

* no-mistakes(document): docs: correct relaunch session-authority ownership and skill paths

* no-mistakes(document): docs: correct stale control-plane ownership claim

* no-mistakes(document): docs: drop unverified Herdr restart resume claim

* no-mistakes(test): Added offline Herdr Pi session-authority relaunch coverage

* no-mistakes(document): Document Herdr Pi relaunch session continuity

* no-mistakes(ci): The failing remote relaunch test tried to arm a PR poll for a secondmate, which `fm-pr-check.sh` correctly refuses. Removed that invalid test scenario; the remaining remote relaunch tests pass, and `git diff --check` is clean
…nchenguid#5758)

Main has been red since fm-pr-check.sh began refusing to arm a merge poll
on a kind=secondmate record (kunchenguid#5696): the relaunch-ordering case in
tests/fm-remote-secondmate-relaunch.test.sh armed its fixture through that
entry point and could no longer be set up.

The ordering guarantee still matters: a secondmate record armed before the
refusal can legitimately carry a trailing pr=/pr_head= identity block until
the watcher retires it, and fm-remote-secondmate-relaunch.sh must still keep
that block last when republishing harness/model/effort. Seed the fixture the
way such a record was really written - pr= appended last to the meta, then
the poll artifacts published through the same
fm_pr_poll_prepare/fm_pr_poll_publish_prepared pair fm-pr-check.sh uses, a
pattern tests/fm-pr-check-security.test.sh already follows - and drop the
now-unused fake gh fixture. The kunchenguid#5696 refusal itself stays pinned by the
security suite's secondmate-record case.
…id#5748)

* feat: run attended supervision on the host for Claude and Cursor

On a home opted into config/supervision-host with a Claude or Cursor
primary, the supervision host now takes the attended wakes the Pi branch
would take: routine outcomes stay off main, and a captain outcome wakes
main once with a branch-outcome line and waits in the drain's new
BRANCH OUTCOMES section until main acknowledges it with mark-processed.

- The offer rule moves into branchOfferForWake, shared by the Pi watcher
  and the host through bin/fm-branch-dispatch.mjs offer.
- The host feeds the dialog mirror at the head of each attended wake and
  passes a close through unchanged when it is main-only, the engine or a
  tool is missing, the primary has no verified mirror, the main session
  cannot be identified, or the session is cooling down.
- The drain presents captain outcomes first, one line per task, never
  behind older routine outcomes, and collapses routine overflow into a
  count that is marked read.
- The return advances the store's read cursor through the away window
  once the brief has rendered, so the first drain does not replay it.
- The branch prompt's mirror wording is host-neutral, and the rule to
  report what main must act on as captain, once per unchanged situation,
  applies only to the attended posture on the host.

* docs: record the attended supervision host live check

* no-mistakes(review): Present pre-window unread outcomes and contiguous captain prefix

* no-mistakes(review): Return brief presents every row it marks read

* no-mistakes(review): Return brief lists every unread outcome in one list

* no-mistakes(review): Keep return list in store order and gate cursor failures

* no-mistakes(review): Make the drain the only branch-outcome presenter after return

* no-mistakes(review): Gate return on drain outcome failures; byte-count outcome budgets

* no-mistakes(review): Gate drain on projection failures; UTF-8-safe byte cuts

* no-mistakes(review): Fail drain without jq; hand unreadable prompt mirror to main

* no-mistakes(document): Correct supervision-host return and drain documentation

* no-mistakes(review): Recheck attended offer at turn start; honest failed-drain brief

* no-mistakes(document): Correct supervision-host posture and drain documentation

* no-mistakes(document): Documentation remains accurate for attended supervision
…unchenguid#5753)

Each '# shellcheck source=' directive makes ShellCheck's external-source
traversal expand that library's whole transitive graph again at the site.
fm-pending-reply-lib carried three directed lazy sources of fm-wake-lib and
two of fm-parent-channel-lib on identical per-call re-source sites, so one
file analysis peaked above 4 GiB and every caller (fm-watch, fm-teardown)
inherited the multiplier - the root cause of the PR kunchenguid#5732 Lint 1 OOM kill.

Keep the runtime '.' commands byte-identical: the lazy re-source under
'local STATE FM_WAKE_QUEUE FM_WAKE_QUEUE_LOCK' is real behavior. Drop the
duplicate directives so each library expands once per unit, and drop the
tmux/classify directives since classify already arrives through the kept
fm-wake-lib expansion and no tmux symbol is referenced here. The directive
above the lib-dir assignment is kept - it binds the bin/ prefix so the
undirected sites still resolve without SC1091.

Measured peak RSS, ShellCheck 0.11.0 -x on Linux arm64:
  bin/fm-pending-reply-lib.sh  4.06 GiB -> 1.96 GiB, zero findings
kunchenguid#5773)

* fix: split bash 5.2 sibling $() in recovery mint and delivery log

Sibling command substitutions on one line can empty a recovery generation
under bash 5.2 when a CHLD trap is set. Mint pid/epoch sequentially, refuse
empty tokens before write, and clean delivery fields before printf.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: split bash 5.2 sibling $() in recovery mint and delivery log

Sibling command substitutions on one line can empty a recovery generation
under bash 5.2 when a CHLD trap is set. Mint pid/epoch sequentially, refuse
empty tokens before write, and clean delivery fields before printf.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: keep recovery mint failure semantics after sibling $() split

Remove the new pid/date refusal and grammar guard so a mint miss still
yields a grammar-valid token and a durable wake row, matching accepted
review intent. Drop the fake-failing-date case that locked in the refuse.

Co-authored-by: Cursor <cursoragent@cursor.com>

* no-mistakes(document): Point recovery-mint hazard comment at its regression test

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…unchenguid#5790)

Fixes kunchenguid#4756

The voice status reader in bin/fm_voice_records.py reports each
worker's state from the last non-blank line of its status log. When a
worker appends a status line and then a line of plain prose, the
reader reported "note" with the prose line instead of the declared
state, diverging from bin/fm-classify-lib.sh's shell scan.

Scan back through the tail for the newest line whose prefix is a
single lowercase verb-shaped word (letters and hyphens), and report
that event's verb instead of always taking the last line. An
unrecognised verb-shaped prefix still reports "note" rather than
letting an earlier recognised line answer for it, and free text with
no colon is skipped as prose. When the tail holds no such event, the
last line is reported exactly as before.
…newer version (kunchenguid#5786)

* fix(bin): stop reporting an already-installed version as an available update

An update announcement named its version first ("current -> new"), so
reading the first dotted number as the announced version compared the
current version against itself and always looked newer. Read the last
dotted number instead, and only report an available update when that
announced version is newer than the newest installed copy found; when
that version is already installed, report only PATH skew.

Fixes kunchenguid#5151

* no-mistakes(document): docs: gate announce update-available report on newer-than-installed
…h their launch config (kunchenguid#5799)

* fix(bin): pass the profile effort to OpenCode workers through their launch config

The dispatch profile's effort axis was recorded in task metadata but never
reached an OpenCode worker: the launch wrote only a permission grant into
the config it constructs.

OpenCode 1.18.32's config schema carries per-model reasoning effort as
agent.<name>.variant, so the chosen effort is now merged into the same
OPENCODE_CONFIG_CONTENT JSON as the default build agent's variant, keyed to
the resolved model. With no effort chosen the launch stays byte-identical.

Fixes kunchenguid#1373

* no-mistakes(review): gate OpenCode effort variant by model provider family

* no-mistakes(document): docs(opencode): note provider-family gating for effort variant
…nchenguid#5815)

* fix: preserve cancellation as no verdict in crew state

Reuse the green-delivery safeguard for cancelled CI monitors and permit a skipped rebase. Other cancelled outcomes and coarse ledger records use the existing unknown state.

Four delivered-PR regressions failed before the fix and pass afterward. The isolated public resolver and fleet-summary tests prove that undelivered cancellation no longer creates a failure contradiction, while preserving historical records and the terminal_in_flight invariant. Evidence uses fixture no-mistakes responses, not a live daemon cancellation.

Update the existing coarse cancellation assertion from failed to unknown because it encoded this defect; retain its newest-run precedence check. Full fm-crew-state suite and pinned lint pass.

* fix(review): Verify PR disposition before reclassifying terminal validation runs

* fix(test): Add captured cancellation replay coverage for resolver and fleet

* fix(document): Clarify cancellation and terminal delivery documentation
…henguid#5812)

* fix: declare worker background and pipeline waits

Require ship and scout workers to declare owned-work waits with the existing
paused verb before ending a turn or waiting on a pipeline or long command.
Keep the first-sight alert and existing liveness classification unchanged;
subsequent inspection follows the existing long pause cadence.

Validation: emitted brief regression failed before the instruction change
and passes afterward. Public watcher/drain regressions cover the first
alert, repeated wedge suppression, bounded rechecks, and undeclared idle
alarms using isolated backend fixtures. Brief suite, pinned lint, Bash
syntax, documentation inventory, and whitespace checks pass.
No real worker harness was exercised for wait behavior.

* fix(document): Clarify declared worker waits and documentation ownership

* fix(ci): Captain, fixed the cadence test to age both the declaration and first-alert throttle while preserving declaration identity. Reproduced the CI failure using stable identity; corrected tests pass with both stable identity and native macOS behavior. Focused ShellCheck and diff checks pass. Production behavior is unchanged; Linux CI was not rerun locally
Conflict resolution:
- Forge plumbing (bin/fm-pr-lib.sh, fm-pr-check.sh, fm-pr-merge.sh,
  fm-pr-poll.sh, tests/fm-pr-check-security.test.sh): upstream added Gerrit
  as a forge beside GitHub and GitLab; the fleet's Gitea/Forgejo provider is
  kept alongside it. The parser now tries GitHub, GitLab, Gerrit, then Gitea;
  fm_pr_gitlab_host_valid follows upstream's rename to fm_pr_forge_host_valid.
- bin/fm-bootstrap.sh: upstream moved the secondmate liveness probe into
  bin/fm-secondmate-liveness-lib.sh; the fleet's alive-but-unsupervised check
  for a local secondmate is ported onto the new alive branch.
- bin/fm-dod-lib.sh: upstream restructured the DoD blocks per forge; the
  fleet's origin/tea wording is reapplied to the direct-PR block while keeping
  upstream's gh-axi draft-check sentence verbatim.
- AGENTS.md and docs: both sides' additions kept; the fleet's FM_HOME
  secondmate-marker note moves into upstream's reorganized FM_HOME section.
The fork's fm-calm.ts imports .pi/extensions/lib/fm-home-resolve.ts, and every
Calm fixture copies it into its sandbox project. Upstream's new queued-row
Escape E2E fixture arrived in this sync without that copy, so the extension
failed to load and Pi never reached the ready composer.
@babbarc
babbarc merged commit 58aad62 into main Sep 26, 2026
18 of 19 checks passed
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.