Skip to content

fix(spawn): refuse a worktree held by a live sibling task without removing it - #249

Open
trillium wants to merge 27 commits into
mainfrom
fix/spawn-refuse-live-sibling-worktree
Open

trillium wants to merge 27 commits into
mainfrom
fix/spawn-refuse-live-sibling-worktree

Conversation

@trillium

Copy link
Copy Markdown
Owner

Intent

The developer, acting through Firstmate operational and steering inbox messages, wanted the coding agent to fix PR kunchenguid#7823 in the firstmate repo. The bug: when fm-spawn.sh refuses a spawn because a live sibling task already holds the worktree, it exits with the Orca abort cleanup still armed, so it removes the sibling's Orca worktree. The fix had to disarm that cleanup on the live-sibling refusal path, keep the sibling's worktree in place, and add a regression test in tests/fm-spawn-worktree-ownership.test.sh that fails without the fix. The change then had to go through the no-mistakes pipeline and be pushed to update the PR. Decisions that needed the captain or Firstmate, such as accepting that live Orca validation was inconclusive because Orca isn't installed on the host, were escalated rather than answered by the agent. Host trust and config changes, like editing known_hosts or changing the fork remote, were also left for Firstmate to authorize. Follow-up PRs kunchenguid#7833 and kunchenguid#7829 were queued but not part of this change.

What Changed

  • bin/fm-spawn.sh now refuses to launch into a worktree (or home) that another task in the same home still records with a positively alive agent endpoint, and names the owning task in the error. A new spawn_worktree_sibling_owner helper scans state/*.meta for the matches. Stale records don't block, and a relaunching task's own record doesn't count against it. The header docs describe the new refusal.
  • The live-sibling refusal path sets ORCA_ABORT_CLEANUP=0 before it exits. Without this, the abort trap would remove the sibling's Orca worktree (Orca removes with --force) and close its terminal.
  • Adds an Orca regression test in tests/fm-spawn-orca-worktree.test.sh. A fake Orca hands back a live sibling's existing worktree, and the test checks that the refusal names the owning task and leaves the worktree and terminal in place. In tests/fm-spawn-worktree-ownership.test.sh, the fake tmux now records window names from new-window and reports them from list-windows, so a task can see its own endpoint as alive.

Risk Assessment

✅ Low: This is a small, well-bounded fix: a positively-alive-only sibling-owner veto, plus disarming ORCA_ABORT_CLEANUP on that refusal so the trap no longer runs orca worktree rm/terminal close against the sibling's resources; the Orca regression test drives the real abort path and fails without the one-line disarm.

Testing

Ran the focused ownership test file and the Orca tests selected on their own. They pass with the fix. Deleting just the new ORCA_ABORT_CLEANUP=0 line makes the Orca regression test fail, as it should. Then I drove the real fm-spawn.sh in a disposable lab home on a private tmux server. With a real claude sibling alive in the target worktree, the spawn was refused: it named task sib-task, and the sibling's window, its claude process and its git worktree were all left in place. Once the sibling window was gone (a stale record), the spawn went ahead. A spawn into a fresh real treehouse slot was also accepted. Orca-backed spawns could only be checked with the repo's fake-orca tests, because the Orca CLI isn't on this host. One cleanup note: the fresh-slot run used real treehouse, which created a pool under ~/.treehouse/proj-e9142a. I returned and destroyed that slot and removed the directory. The lab was torn down, and no changes or temporary files remain in the worktree. This is a CLI change with no UI, so there are no screenshots; the evidence is CLI transcripts.

  • Live validation: ✅ go - 3 of 6 scenarios driven live against the product
Scenario Result Live Evidence
A tmux spawn whose pane settles in a worktree held by a live sibling task (real claude running) is refused, names the sibling, and leaves its window and worktree in place ✅ pass live live-tmux-live-sibling-refused.txt: 'error: treehouse get yielded task sib-task (recorded in state/sib-task.meta) ... refusing to launch into a worktree held by a live sibling task', EXIT=1; fm-sib-ta…
A stale sibling record (its window is gone) does not block a spawn into that worktree ✅ pass live live-tmux-stale-sibling-not-blocking.txt: 'spawned new-task-a1 ... worktree=.../sib-wt', EXIT=0
A spawn into a fresh, unowned treehouse slot is still accepted ✅ pass live live-tmux-fresh-slot-accepted.log: 'spawned new-task-a1 harness=claude ... worktree=~/.treehouse/proj-e9142a/1/proj', EXIT=0
An Orca spawn that Orca hands a live sibling's worktree is refused without removing that worktree or closing its terminal ⏸️ untested no The orca CLI is not on PATH and there's no copy in the repo, so a real Orca backend can't be run here. The real fm-spawn.sh was run only against the test's fake orca (orca-selected-tests.log), which…
Adversarial: with the one-line fix removed, the regression test catches the sibling's Orca worktree being deleted ⏸️ untested no The earlier payload did not establish a live result here. This was a check of the regression test against the repo's fake orca (orca-regression-without-fix.log, adversarial-revert.diff), not of live p…
Normal Orca fresh spawn and relaunch refusal are unchanged ⏸️ untested no Same blocker: the Orca CLI isn't installed on this host, so only the fake orca could be used (the fresh-spawn and relaunch-refusal tests in orca-selected-tests.log pass). That is not live.
Evidence: Live tmux: spawn into a live sibling's worktree is refused; sibling window and worktree kept

Source: Live tmux: spawn into a live sibling's worktree is refused; sibling window and worktree kept

== spawn transcript ==
fm-gate-refuse: gate agent lifecycle permitted only against lab home /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN
WARNING: watcher still down (same stale episode; last beat: never, grace 300s) - full banner already printed this episode.
warning: /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/data/new-task-a1/launch-brief.md records no ship branch; defaulting to legacy branch fm/new-task-a1
warning: /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/data/new-task-a1/launch-brief.md records no delivery contract line (scaffolded before ship briefs recorded one); launching on the explicit --mode no-mistakes - confirm its definition of done matches
error: treehouse get yielded task sib-task (recorded in state/sib-task.meta) (resolved '/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/sib-wt'); refusing to launch into a worktree held by a live sibling task. Inspect target firstmate:fm-new-task-a1
EXIT=1

== lab tmux windows after refusal ==
ctl cmd=bash cwd=~/.no-mistakes/worktrees/0bf32a15afdb/01M65E95KCXFA28D7HM013TEQ6
fm-sib-task cmd=claude cwd=/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/sib-wt
fm-new-task-a1 cmd=bash cwd=/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/sib-wt

== sibling worktree still registered ==
/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/proj    711a7fb [main]
/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/sib-wt  711a7fb [sib]
.
..
.git

== sibling pane (live claude) ==
────────────────────────────────────────────────────────────────────────────────
 Accessing workspace:
 /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/sib-wt
 Quick safety check: Is this a project you created or one you trust? (Like your
 own code, a well-known open source project, or work from your team). If not,
 take a moment to review what's in this folder first.
 Claude Code'll be able to read, edit, and execute files here.
 Security guide
 ❯ No, exit
   Yes, I trust this folder
 Enter to confirm · Esc to cancel
Evidence: Live tmux: a stale sibling record does not block the spawn

Source: Live tmux: a stale sibling record does not block the spawn

== sibling record present but its window is gone ==
window=firstmate:fm-sib-task
worktree=/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T//fm-lab.gxg6nN/sib-wt
backend=tmux
kind=ship
harness=claude
mode=no-mistakes
yolo=off

== spawn transcript ==
fm-gate-refuse: gate agent lifecycle permitted only against lab home /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN
WARNING: watcher still down (same stale episode; last beat: never, grace 300s) - full banner already printed this episode.
warning: /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/data/new-task-a1/launch-brief.md records no ship branch; defaulting to legacy branch fm/new-task-a1
warning: /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/data/new-task-a1/launch-brief.md records no delivery contract line (scaffolded before ship briefs recorded one); launching on the explicit --mode no-mistakes - confirm its definition of done matches
spawned new-task-a1 harness=claude kind=ship mode=no-mistakes yolo=off window=firstmate:fm-new-task-a1 worktree=/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/sib-wt
EXIT=0

== lab tmux windows ==
ctl cmd=bash cwd=~/.no-mistakes/worktrees/0bf32a15afdb/01M65E95KCXFA28D7HM013TEQ6
fm-new-task-a1 cmd=claude cwd=/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/sib-wt
Evidence: Live tmux: a fresh real treehouse slot is accepted

Source: Live tmux: a fresh real treehouse slot is accepted

fm-gate-refuse: gate agent lifecycle permitted only against lab home /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  WATCHER DOWN - SUPERVISION IS OFF
●  1 task(s) in flight, but no watcher has a fresh beacon (last beat: never, grace 300s).
●  Trust the emitted supervision protocol for this harness; do not use shell & for watcher repair.
●  This is a supervision warning only; the guarded operation WILL still run.
●  watcher supervision needs Stop-owned automatic recovery; inspect the hook registration and startup status before ending the turn.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
warning: /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/data/new-task-a1/launch-brief.md records no ship branch; defaulting to legacy branch fm/new-task-a1
warning: /private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-lab.gxg6nN/data/new-task-a1/launch-brief.md records no delivery contract line (scaffolded before ship briefs recorded one); launching on the explicit --mode no-mistakes - confirm its definition of done matches
spawned new-task-a1 harness=claude kind=ship mode=no-mistakes yolo=off window=firstmate:fm-new-task-a1 worktree=~/.treehouse/proj-e9142a/1/proj
EXIT=0
Evidence: Orca regression test fails with the fix line removed

Source: Orca regression test fails with the fix line removed

not ok - the refusal removed the live sibling's Orca worktree

ok - real tmux preserves absent/C locale values and reported raw bytes, rejects missing/phantom names and excludes the automatic SSH agent
not ok - the refusal removed the live sibling's Orca worktree
Evidence: Temporary revert used for the adversarial run

Source: Temporary revert used for the adversarial run

diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh
index d81db5bb..116d62a7 100755
--- a/bin/fm-spawn.sh
+++ b/bin/fm-spawn.sh
@@ -4349,7 +4349,6 @@ validate_spawn_worktree() { # <source> <inspect-target>
   if [ -n "$wt_real" ] && owner=$(spawn_worktree_sibling_owner "$wt_real"); then
     # The live sibling owns this worktree and its terminal, so the abort trap
     # must not close or remove either (Orca removes with --force).
-    ORCA_ABORT_CLEANUP=0
     echo "error: $source yielded $owner (resolved '$WT'); refusing to launch into a worktree held by a live sibling task. Inspect target $inspect_target" >&2
     exit 1
   fi
Evidence: Selected Orca tests with the fix

Source: Selected Orca tests with the fix

ok - real tmux preserves absent/C locale values and reported raw bytes, rejects missing/phantom names and excludes the automatic SSH agent
ok - an Orca spawn refused for a live sibling's worktree leaves that worktree and terminal in place
ok - an Orca-backed fresh spawn enters the worktree Orca created for it, instead of hard-refusing on the post-launch proof
ok - a relaunch against an Orca-backed task is refused before the RELAUNCH+orca worktree carve-out could run
EXIT=0
Evidence: Ownership test file output

Source: Ownership test file output

warning: re-init: ignored --initial-branch=main
On branch main
nothing to commit, working tree clean
fatal: destination path '/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-spawn-worktree-ownership.dpIQgM/sibling-task/project.origin.git' already exists and is not an empty directory.
error: remote origin already exists.
ok - a pane stably sitting in a live sibling task's worktree is refused
warning: re-init: ignored --initial-branch=main
On branch main
nothing to commit, working tree clean
fatal: destination path '/private/var/folders/d9/2d5w27z5775c_r1qcw57vzzr0000gn/T/fm-spawn-worktree-ownership.dpIQgM/stale-record/project.origin.git' already exists and is not an empty directory.
error: remote origin already exists.
ok - a stale sibling task record does not veto a fresh allocation
ok - a fresh, unowned worktree is accepted
ok - the task's own recorded worktree is accepted
# all fm-spawn-worktree-ownership tests passed

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 1 issue found → auto-fixed ✅
  • ⚠️ tests/fm-spawn-worktree-ownership.test.sh - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 3 of 6 scenarios driven live against the product
Scenario Result Live Evidence
A tmux spawn whose pane settles in a worktree held by a live sibling task (real claude running) is refused, names the sibling, and leaves its window and worktree in place ✅ pass live live-tmux-live-sibling-refused.txt: 'error: treehouse get yielded task sib-task (recorded in state/sib-task.meta) ... refusing to launch into a worktree held by a live sibling task', EXIT=1; fm-sib-ta…
A stale sibling record (its window is gone) does not block a spawn into that worktree ✅ pass live live-tmux-stale-sibling-not-blocking.txt: 'spawned new-task-a1 ... worktree=.../sib-wt', EXIT=0
A spawn into a fresh, unowned treehouse slot is still accepted ✅ pass live live-tmux-fresh-slot-accepted.log: 'spawned new-task-a1 harness=claude ... worktree=~/.treehouse/proj-e9142a/1/proj', EXIT=0
An Orca spawn that Orca hands a live sibling's worktree is refused without removing that worktree or closing its terminal ⏸️ untested no The orca CLI is not on PATH and there's no copy in the repo, so a real Orca backend can't be run here. The real fm-spawn.sh was run only against the test's fake orca (orca-selected-tests.log), which…
Adversarial: with the one-line fix removed, the regression test catches the sibling's Orca worktree being deleted ⏸️ untested no The earlier payload did not establish a live result here. This was a check of the regression test against the repo's fake orca (orca-regression-without-fix.log, adversarial-revert.diff), not of live p…
Normal Orca fresh spawn and relaunch refusal are unchanged ⏸️ untested no Same blocker: the Orca CLI isn't installed on this host, so only the fake orca could be used (the fresh-spawn and relaunch-refusal tests in orca-selected-tests.log pass). That is not live.
  • bash tests/fm-spawn-worktree-ownership.test.sh (live sibling refused, stale record not blocking, fresh worktree accepted, the task's own worktree accepted)
  • Selected Orca tests run on their own: test_orca_live_sibling_worktree_refusal_keeps_the_sibling_checkout, test_orca_fresh_spawn_enters_the_worktree_it_created, test_orca_relaunch_is_refused_before_the_worktree_carveout_could_run
  • Adversarial: deleted only the ORCA_ABORT_CLEANUP=0 line at bin/fm-spawn.sh:4352, reran the Orca regression test (it fails: 'the refusal removed the live sibling's Orca worktree'), then restored the file
  • Live lab (lab home from bin/fm-lab-home.sh create, private tmux -L fm-lab socket): real bin/fm-spawn.sh new-task-a1 &lt;lab-proj&gt; with a real claude sibling window recorded in state/sib-task.meta, and a lab-local treehouse shim that drops the pane into the sibling's worktree
  • Live lab: killed the sibling window so its record is stale, then reran the real spawn into the same worktree
  • Live lab: an earlier real spawn through real treehouse got a fresh slot and was accepted (the slot was then returned and destroyed with treehouse, and its pool directory removed)
  • Teardown: tmux -L fm-lab kill-server, rm -rf $LAB, checked that no stray treehouse pool and no changes in the worktree remain
✅ **Document** - passed

✅ No issues found.

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

✅ No issues found.

mikeastwoody and others added 23 commits October 28, 2026 11:20
… work (kunchenguid#7352)

* feat(bin): add opt-in config/spawn-base-primary-checkout for local landings

A fresh pooled task worktree is reset to origin/<default>. A local-only
project lands work on the primary checkout's default branch through
fm-merge-local.sh and never pushes, so that ref stays frozen at the last
push and every fresh task starts behind work already landed, loading the
project's instruction files from the stale tree. The always-on fix proposed
in kunchenguid#3812 replaced the specified origin-only refresh for every home; this
rework keeps that rule as the default and moves the new base behind an
explicit, default-off presence flag, following the existing config/<flag>
pattern.

With config/spawn-base-primary-checkout present:
- fm-spawn.sh starts a fresh local-only ship, or a scout without
  --base-branch, from the primary checkout's <default> when it strictly
  contains origin's tip. Publishing modes and named base branches keep
  origin's tip, because that work is delivered against origin. A diverged
  primary keeps origin's tip and prints a note counting both sides. The
  commit is read from the local object store, so nothing extra is fetched,
  and the clean-slot refusal still runs first.
- fm-review-diff.sh anchors a remote-backed task without base_branch= on the
  branch it delivers to: publishing modes stay on origin/<default>, while a
  local-only ship or a scout is compared against whichever of the local
  <default> and origin/<default> meets the branch later, so the review shows
  the task's own work instead of every locally landed commit.
- fm-promote.sh tells a scout promoted to a publishing mode to return to
  origin's default branch, because in this home the scout may have started
  from the primary checkout's branch, whose unpushed commits would otherwise
  be published with the work.

fm-dod-lib.sh gains fm_mode_publishes as the one predicate those three
readers share. Without the flag every path is unchanged: the spawn resets
to origin's tip, the review anchors on origin/<default>, and promotion keeps
its wording; the existing tests run unmodified. Treehouse 3 (verified on
3.1.3) already hands a slot out at whichever of the local and remote default
branch is further ahead, so with the flag the refresh keeps that slot where
Treehouse put it for a local landing instead of rewinding it, and a project
without origin is untouched as before.

Supersedes kunchenguid#3812.

* no-mistakes(document): Drop change-relative wording from spawn-base config docs

* no-mistakes(ci): The Greptile Review check failed on two review findings, not on a broken build or test. I fixed both as the user directed, and the tests and lint pass. ci-1 (bin/fm-promote.sh): promotion must name origin's default branch only when spawn could actually have started the scout from the primary checkout. Spawn does that only when the project has an origin remote and is recorded on the task. Before this fix, the opt-in flag alone made promotion to no-mistakes or direct-PR say "copy of origin's default branch...", even on a project with no origin. - The new wording now also requires a recorded project (`[ -n "$PROMOTE_PROJECT" ]`) whose `git remote get-url origin` succeeds. That is the same check bin/fm-review-diff.sh already makes. The empty-project guard is needed because `git -C ""` would run in the current directory. Otherwise the existing "default-branch base" wording stays. - The three places that read the flag now agree: spawn, review-diff and promote. - I updated the header and inline comments in fm-promote.sh, and the promote line in docs/configuration.md, to say this applies only to a project with an origin remote. - Test: `test_promote_names_origin_for_a_publishing_mode_in_an_opted_in_home` in tests/fm-task-delivery.test.sh now records a real `project=` with a registry entry, and covers flag on/off × origin/none × direct-PR/no-mistakes/local-only. Only flag on + origin + a publishing mode gets the new wording. The named-base case is unchanged. - Before the fix, the test fails with "not ok - flag on, remote none, direct-PR: the promotion changed the default-branch wording". With the fix, it passes. ci-2 (tests/fm-spawn-pool-base-freshen.test.sh): with the flag set, both publishing modes must start from origin's tip and print no divergence note. - The opted-in spawn test now covers no-mistakes as well as direct-PR, local-only and scout. - The expectations no longer single out direct-PR. Both publishing modes now expect origin's tip, no "note: pooled worktree", and a review anchored at the slot's start, in the leading, diverged and trailing cases. Verification: - `bash tests/fm-task-delivery.test.sh`: exit 0. - `bash tests/fm-spawn-pool-base-freshen.test.sh`: exit 0. - `bin/fm-lint.sh` on the three changed shell files: exit 0 (ShellCheck 0.11.0, pinned, with extended analysis)
…enguid#7915)

* fix(teardown): distinguish unavailable content-landed proof

* no-mistakes(document): Clarify teardown proof failure documentation

* no-mistakes(ci): Captain, CI failed in unchanged watcher recovery code, independent of this PR. The focused failing case passed locally with exit 0. The full suite stalled earlier and was stopped. No tracked changes. Command guard rejected cleanup; scratch remains at .ci-7915/ and tests/.ci-7915-cycle.sh

* chore: untrack CI validation scratch
…kunchenguid#7876)

* feat(dispatch): prefer configured provider family

* no-mistakes(review): drop policy_why field and dead auth fixture from preference tests

* no-mistakes(document): docs: record preferred_provider in schema, diagnostics, verification
)

* fix: pin local-only landing to its accepted done head

Persist the accepted local-only commit in task metadata and refuse landing when the branch or the recorded head changes. Merge the full commit ID under the task control and metadata locks.

Closes kunchenguid#7782

* no-mistakes(test): Refuse local landing races with atomic ref transactions

* no-mistakes(document): Document pinned local landing and recovery contracts

* no-mistakes(ci): Applied only the verified incremental patch and preserved existing repairs. Complete diff matches the supplied candidate hash. Both focused test files, default lint, and source-aware lint passed with exit 0. Evidence retained at /tmp/up7782-local-repair/ci-phase-fresh-*. No commits, pushes, or pipeline-control actions performed; publication remains with the outer executor

* no-mistakes(ci): Fixed both SC2031 false positives in tests/fm-merge-local.test.sh with narrowly scoped suppressions. Tier 2 lint correction; runtime changes were unnecessary. Default lint, source-aware lint, all 8 focused tests, and git diff --check passed. No commits, pushes, or pipeline-control actions performed
…henguid#7928)

* fix(procevent): coalesce unhandled extension host errors

* no-mistakes(document): Document host-error coalescing and recovery semantics
…uid#7912)

* fix: stop wake grants when lock acquisition fails

* no-mistakes(document): Document wake grant lock acquisition failure behavior
* fix: send remote steers over stdin

* fix: version remote stdin send protocol

* no-mistakes(document): document stdin transport for remote steer in fm-send header

* no-mistakes(ci): Two tests failed in CI. One is caused by this PR; the other is not. 1. Behavior portable serial 5: `tests/fm-secondmate-reconcile.test.sh` failed with "supervision did not deliver the durable reconcile request later". This PR caused it. The PR now sends a remote steer's body on stdin, using `fm-on.sh --stdin` with the `send-stdin` verb. This test's fake ssh started with `cat > /dev/null`, so it threw the body away before it ran the real `fm-remote-secondmate-control.sh`. `cmd_send_stdin` then read an empty message, died with "remote steer message must be nonempty", and no inbox record was written. Rule that broke: a fake ssh that runs the real remote leg must pass the caller's stdin through, as real ssh does. I checked every fake ssh that runs the real leg. `fm-send-remote-delivery.test.sh` already passes stdin through. `fm-secondmate-sync.test.sh` (`make_remote_leg_ssh_stub`) had the same discard line. The stub in `fm-send-resolve-key.test.sh` never runs the leg, so it is fine. The fix deletes the `cat > /dev/null` line in the reconcile and sync stubs. Calls made without `--stdin` still get /dev/null from fm-on, so nothing else changes. Checks run locally: the reconcile test fails before the fix with the same CI message. After the fix, the reconcile test passes (rc=0) and the sync test passes (rc=0). 2. Behavior portable serial 3: `tests/fm-wake-queue.test.sh` failed in `test_concurrent_append_and_drain` with "wake drain: queue lock could not be acquired safely". This PR did not cause it. The PR does not touch `bin/fm-wake-drain.sh`, the wake tests or the wake helpers. The failure is lock contention: 40 appenders run at the same time as a drain, on a loaded CI runner. Locally the whole test passed (70 ok, rc=0). I made no change for it. It is an existing flake in wake-queue lock acquisition and needs its own fix outside this PR. Open PR kunchenguid#7912 ("preserve wake grants when queue-lock acquisition fails") works in the same area
…owner manages (kunchenguid#7357)

* fix(herdr): never start a server an external owner manages or is replacing

fm_backend_herdr_server_ensure started a detached herdr server whenever
status reported the session not running. When a service manager owns the
server, that races a unit restart or a server live-handoff and splits panes
across two servers.

The session directory reported by herdr session list can now carry a
firstmate-server-owner marker (persistent, naming the owner) and a
firstmate-server-replacing marker (for the duration of a restart or handoff).
While either exists and the server is not running, server_ensure never starts
a server: it waits a bounded time (FM_BACKEND_HERDR_EXTERNAL_SERVER_POLLS,
default 30s) for the server to report running, and otherwise fails naming the
owner or the replacement. A running server is still used at once, and a
session with no marker keeps the Firstmate-owned autostart unchanged.

Fixes kunchenguid#7320

* no-mistakes(review): Enforce herdr replacement marker on fast path; fixed wait bound

* no-mistakes(ci): Both CI failures had one cause in this PR, and I fixed it in code; no test assertions changed. `fm_backend_herdr_server_ensure` had started running `herdr session list --json` on every call to find the session directory for the ownership markers. Two existing tests treat that call as a sign of something else: - `tests/fm-control-relaunch.test.sh` failed with "a herdr reclaim touched a session the record does not name: session list --json", because it allows only `--session fmlab` calls and `status --json`. - `tests/fm-backend-herdr-presentation-e2e.test.sh` failed with "opted-out spawn added presentation-ordering capability or socket calls", because there `session list` means presentation ordering ran. Invariant: `server_ensure` must not make Herdr calls beyond its own `status` read, so ordinary spawns and reclaims stay free of `session list` and only touch the session they name. Every caller goes through `server_ensure`, so fixing it there covers all of them. Fix (`bin/backends/herdr.sh`): the session directory now comes from the `status --json` read `server_ensure` already makes. It is the directory containing `.server.socket`. I removed the `fm_backend_herdr_session_dir` helper. On a running server, the only added work is one check for the replacement marker, which is what the user asked for. I checked this against real Herdr 0.9.5: - `status` reports the socket even when the server is stopped, for both the default and named sessions. - The socket's directory is the same `session_dir` that `session list` reports, and it follows `XDG_CONFIG_HOME`. - With `--session`, an inherited `HERDR_SOCKET_PATH` does not change it. - The read creates nothing on disk. If a Herdr version omits the socket, `server_ensure` keeps the old Firstmate-owned behavior, as the docs already said for a missing `session_dir`. Other changes: the marker-test fakes now report the socket under the session directory, and I removed the `session list` stub this PR had added to the shared fake. `docs/herdr-backend.md` and `docs/verification/runtime-backends.md` ("Session directory") now describe and record the `status`-socket measurement. Verification: - `tests/fm-backend-herdr.test.sh` passes: 239 ok, 0 not ok, including all the externally-managed and replacement-marker tests. - `tests/fm-control-relaunch.test.sh` passes; it was the shard 3 failure. - shellcheck shows only existing info-level notes. Not verified locally: the real-Herdr presentation test. Here it stopped earlier, at an unrelated Treehouse timeout ("treehouse get did not enter an isolated worktree within 60s"). After the fix `server_ensure` no longer runs `session list`, which is the call that assertion caught. No lab session was left running, and I removed the test's temp directory
)

* fix: classify actionable heartbeat backstop as a check

* no-mistakes(document): Correct heartbeat routing docs and remove stale test count

* fix: preserve heartbeat routing when resetting actionable backoff
…mputes (kunchenguid#7931)

* fix(bin): reclaim a lock whose recorded pid-identity no longer recomputes

kunchenguid#7886 made fm_lock_holder_alive reject a live pid whose start time no
longer matches the lock's recorded pid-start. A holder that predates that
record still falls back to pid liveness, which leaves issue kunchenguid#7801 open: on
a /proc-less host outside UTC, a watcher started before fm_pid_identity
pinned TZ=UTC0 recorded a local-time pid-identity. After the update that
live holder no longer matches its own identity, so the health predicate
rejects it, yet every new watcher stands down beside it and the home stays
unsupervised.

fm_lock_holder_alive now also requires a recorded pid-identity, when
present, to still recompute for the pid, after the existing start-time
check and with the same missing-evidence fallbacks: no record, a pid
rewritten mid-read, or an unreadable value keeps the pid-liveness answer.
Both records go through one helper, _fm_lock_record_matches. Only
long-lived holders that never exec write a pid-identity (the watcher, the
supervise daemon), so a genuine holder still matches. A watcher replaced
this way stands down unsignalled at its next poll through the existing
self-eviction. The fm_pid_identity comment and docs/turnend-guard.md
claimed the upgrade-window residual is cleared by the post-update restart,
which never reaches a primary home's own watcher; they now say the next
lock claim clears it.

Verification:
- Drove: real bin/fm-watch.sh processes with /proc hidden through
  FM_PROC_ROOT_OVERRIDE and TZ=America/Los_Angeles: a live genuine watcher
  whose lock record was rewritten to its own local-time ps lstart identity
  with no pid-start, as a pre-pin build wrote it, then a fresh watcher;
  plus fm_lock_try_acquire against a live pid with a mismatched, matching,
  and unrecorded pid-identity and no pid-start.
- Observed: on the unchanged library the fresh watcher exits "lock held by
  live pid ... but heartbeat is stale" and fm_lock_try_acquire returns
  held for the mismatch. With this change the fresh watcher takes the
  lock, the legacy holder exits 0 by standing down, the live pid is never
  signalled, and matching or unrecorded identities stay held.
- Re-check: tests/fm-watcher-lock.test.sh cases
  test_watch_replaces_live_holder_with_legacy_local_time_identity and
  test_lock_reclaims_holder_whose_identity_no_longer_recomputes.

* no-mistakes(review): Limit pid-identity lock reclaim to the watch lock

* no-mistakes(document): Correct fm_lock_holder_alive comment on non-watch identity locks

* no-mistakes(ci): I made two small fixes. CI and "Require no-mistakes" never ran: GitHub shows both as `action_required`, waiting for approval to run workflows from a fork, so the code didn't cause those. Greptile failed because it scored the PR 4/5 against a required 5/5, with one P2 comment. 1. **Greptile P2, test children left running (`tests/fm-watcher-lock.test.sh`).** The two new lock tests start a `sleep 300` holder that only gets killed after every assertion passes, and the suite's cleanup only reaps processes whose command line contains the fixture root. Each holder now starts as `(exec -a "$dir/holder" sleep 300)`, so cleanup reaps it if the test fails early. To check, I forced a `fail` right after the child starts: on HEAD the `sleep 300` was still running afterwards, and with the fix it was reaped. Both tests pass normally. 2. **Flaky takeover test (`bin/fm-watch.sh`).** This edit was already uncommitted in the worktree. Twelve isolated runs passed without it, so I first dropped it as out of scope, but the full suite then failed on exactly this race. When a successor takes over a live watcher's lock, it deletes the lock and recreates it while holding `.watch.lock.steal`. If the old watcher checks during that gap, it sees no lock `pid` and exits as "state directory was torn down", not by standing down cleanly as the PR intends. That race only happens once this PR lets a healthy, live watcher's lock be taken. The guard treats a missing `pid` as teardown only when no `.steal` marker exists, with a comment explaining why. With it restored, 15 runs of the takeover test (three at a time, to add load) all passed, and all three PR tests passed in the full suite. **Remaining failures:** with the guard restored, I ran the full suite three times and it failed each time, but always on tests this change doesn't touch: "bounded child cleanup regression" twice and "bounded ledger cycle 2 did not start" once. The host's load average was about 100, and these tests depend on timing; one of them runs before any watcher starts. I didn't confirm they also fail on the base commit. shellcheck reports nothing in the test file beyond info notes about files it didn't follow, and one existing warning in `fm-watch.sh` at line 2598, not from this change. I deleted the leftover temporary runner `tests/.tmp-one.sh`. Nothing is committed or pushed

* fix(bin): re-read the watch lock holder after the steal check

The steal-gap guard read the lock pid, then the steal mutex. A successor
that removed and relinked the lock between those reads let the old
watcher see no pid and then no steal mutex, so it exited as "state
directory was torn down" instead of standing down. Upstream CI hit this
in tests/fm-watcher-lock.test.sh
(test_watch_replaces_live_holder_with_legacy_local_time_identity).

The teardown branch now re-reads the pid after the steal mutex. The
steal mutex is held for the whole remove-and-relink, so a steal that
finished between the first two reads has already relinked the lock by
the third read, and the self-eviction below stands the watcher down.

Verification:
- Drove: the takeover test (real bin/fm-watch.sh processes) 48 times,
  six at a time, with and without this change, plus the whole
  tests/fm-watcher-lock.test.sh file.
- Observed: the interleaving is too narrow to hit locally (48 of 48
  passed either way); the whole file passes with this change. The
  failing CI run shows the old watcher's exit 1 teardown message.
- Re-check: Behavior portable serial 6 on this PR's CI.

* no-mistakes(review): Scope fm_pid_identity residual comment to the watch lock

* no-mistakes(review): Align fm_pid_identity residual comment with autoarm reclaim behaviour

* no-mistakes(document): Note watch-lock identity mismatch skips lock stale grace

* fix(bin): tell a released watch lock from a torn-down state directory

A watcher replaced by a successor still exited 1 as "state directory was
torn down" on upstream CI. The successor that reclaims the lock delivers
its recovery wake and exits, releasing the lock, and on a fast runner
that happens before the old watcher's next poll. The old watcher then
found no lock holder and no steal in progress, which the teardown check
read as a recreated state directory.

The watcher now holds its state directory open (fd 6) from startup, and
a lock with no holder is a teardown only when the directory at that path
is no longer the one it holds; otherwise the lock was released by a
successor and the self-eviction stands the watcher down with exit 0. The
held descriptor keeps the inode from being reused by a recreated
directory. A host that cannot open a directory keeps the old reading.
This replaces the steal-marker check and pid re-read, which only covered
a successor still relinking the lock.

Verification:
- Drove: real bin/fm-watch.sh processes. A new regression,
  test_watch_replaced_holder_stands_down_after_successor_exits, pauses
  the old holder (SIGSTOP, only while it holds no other state lock) until
  the successor has delivered its wake and exited, then resumes it. Also
  a standalone run with the old watcher polling every 5s.
- Observed: on the previous head the old watcher exits 1 "state
  directory was torn down (singleton lock removed)", the CI failure,
  every time; with this change it exits 0 by standing down. Both
  takeover tests passed 20 of 20 runs four at a time on one loaded CPU.
  The teardown tests in tests/fm-watch-arm.test.sh (state directory and
  home removed) still pass.
- Re-check: tests/fm-watcher-lock.test.sh and Behavior portable serial 6.

* no-mistakes(test): Hold the watcher's state directory open before claiming the lock

* no-mistakes(document): Docs already match watch-lock identity reclaim change

---------

Co-authored-by: mangocabs2025 <250559406+mangocabs2025@users.noreply.github.com>
…nguid#7911)

* Fix non-GNU timeout fallback

* no-mistakes(review): Assert INT reaches watcher in non-GNU timeout fallback test

* no-mistakes(document): Document GNU-only timeout selection in watch checkpoint

* no-mistakes(ci): Greptile flagged that only the non-GNU-timeout-to-Perl path had an isolated test. I added two isolated tests in tests/fm-watch-checkpoint.test.sh, as you asked. The production code is unchanged. The rule being tested: run_bounded must run GNU `timeout` first, then GNU `gtimeout`, then Perl. Whichever GNU command it runs must receive `--signal=INT --kill-after=<bound>s <seconds> <watcher>`. What changed: - **Shared setup:** a new helper, `make_isolated_checkpoint`, copies the checkpoint into a temporary root. It also builds a PATH that holds only the named host tools, so the stubs decide which timeout is found. The existing Perl-fallback test now uses this helper too. Its assertions are the same. - **Stubs:** a non-GNU stub reports `uutils` for `--version` and exits 137 if it is ever used. A GNU stub reports `GNU coreutils` and writes its own name and arguments to `$FM_HOME/timeout-calls`, then exits 124. - **`test_gnu_timeout_runs_the_watcher_bound`:** GNU `timeout` and GNU `gtimeout` are both on PATH. The test asserts exit 124, the quiet checkpoint line, and exactly one call: `timeout --signal=INT --kill-after=5s 1 <root>/bin/fm-watch.sh`. - **`test_non_gnu_timeout_uses_gnu_gtimeout`:** a non-GNU `timeout` is next to a GNU `gtimeout`. The test asserts the same exact call, made by `gtimeout`. - In both new tests, Perl is not on PATH. So if the code picked Perl by mistake, the run would fail with 127. How I checked it: - The full test file passes: all 14 tests. - I broke the code on purpose twice. When I swapped the order to try `gtimeout` first, the GNU timeout test failed. When the code always picked Perl, the run exited 127 instead of 124. I then put the production file back; `git status` shows only the test file changed. - `shellcheck -x` passes on the test file. This is a change to tests only, so there is nothing a user would see to check by hand
* fix: recover ownerless extension readiness and announce cleanup refusals

* no-mistakes(review): Reset cleanup failure episodes and classify failure wakes

* no-mistakes(document): Document ownerless readiness recovery and cleanup failure wakes

* no-mistakes(document): Clarify cleanup failure documentation ownership

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
kunchenguid#7874)

* Keep malformed remote reply captures from wedging

* no-mistakes(document): Fix garbled remote reply source failure comment

* no-mistakes(document): Document remote reply failure handling

---------

Co-authored-by: Pitch Perfekt Collective <pitchperfekt@Pitchs-Mac-Studio.local>
…n PR text (kunchenguid#7937)

* feat(bin): per-project pr=manual registry token for hand-written PR text

A no-mistakes ship on a project where an outside maintainer reviews the PR let
the pipeline's pr step rewrite the hand-written title and body on every
validated push. The registry now records pr=manual, resolved by
fm-project-mode.sh --pr and carried by fm-brief.sh --pr, fm-spawn.sh and
fm-promote.sh like forge=. The generated brief skips pr and ci, and the worker
writes and publishes the PR text itself.

* no-mistakes(review): Drop pr_text key; fix manual-PR checks wait and base

* no-mistakes(review): Remove dead PR_TEXT and timeout example

* no-mistakes(document): Fix README pr=manual sentence grammar

* test(bin): restore the displaced placeholder comment and rename the spawn agreement test

The comment for the leftover-placeholder test sat above the new pr=manual tests.
The spawn test said it recorded the PR text, but it only checks that a launch happens.
* fix(teardown): detect untracked work regardless of Git status config

* test(watcher): avoid blocking FIFO contention handshakes

* no-mistakes(document): Document hidden-untracked teardown safety

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
* fix: prefer current run over stale overview row

* fix: preserve newest run precedence over stale status

* no-mistakes(document): Document live-run precedence in crew-state attribution

* fix: use ledger order for same-minute run precedence

* test: cover both overview orders for same-minute run attribution

Document and pin newer-first (recorded AXI) and historical-first overview
orders. Same-minute ledger timestamps use row order as the tie-break so an
older live bare status cannot hide a newer completed run.

* no-mistakes(review): Treat failed as overridable terminal overview status

* no-mistakes(review): Fix equal-head live run precedence with identity ordering

* no-mistakes(document): Updated run identity ordering documentation

* no-mistakes(lint): Fix ShellCheck export warning

* no-mistakes(review): Prove same-head ordering or report unknown

* no-mistakes(review): Guard failed selected-run reads and drop dead same-head ordering

* no-mistakes(ci): I fixed both Greptile findings (ci-1, ci-2) in bin/fm-crew-state.sh. The change is in the working tree. I did not commit it, push it, or reply to the Greptile threads, because those steps belong to the outer pipeline. ci-1. Invariant: a live run from bare `axi status` may replace a terminal overview selection only when it is proven newer than every other run on the same branch. Previously the code compared it only against the selected run. Now crew-state reads each other candidate with `axi status --run <id>` and compares the live run against each one: - If another candidate is also live, it reports `unknown`, "competing live runs". This keeps the competing-live-runs check. - If two runs share a head and a ULID timestamp, it reports `unknown`, "same-head run ordering unverified", as before. - It promotes the live run only when it is proven newer than all other candidates. - If the live run is newer than the selected run but not proven newer than some other candidate, it reports `unknown`, "run ordering unverified", and names every run id. This means neither the stale selected run nor the live run can hide a newer failed run. This branch is the one judgement call: without it, the stale "done" row A would still hide C's failure. - In all other cases the overview's selection stands. The lib helper is now a pairwise compare called `fm_nm_run_is_newer_than` (it was `fm_nm_run_is_newer_than_selected`). The selected run's detail from the loop is reused when the read succeeded, as in the round-1 fix. I updated the sentence in docs/architecture.md to match. ci-2. Invariant: the `no-mistakes runs` ledger is read only to order runs with different heads. It is now fetched lazily, at most once, and only when a candidate's head differs from the live run's head. Same-head ULID comparisons never call it. Regression test: `test_live_run_newer_than_selection_does_not_hide_newer_failed_run` covers A (stale completed, listed first), C (newer failed) and B (live, created between them). All three share one head and use ULID ids. It expects `state: unknown`, C's id in the output, and no `state: working`. Before the fix it fails: with the fixed code stashed it printed `state: working ... run: 01M1...` (run B). On the original base 3fbace9 the overview would select A, so the output would be `done`, which also fails the `unknown` check. I did not run that base case. Verification: `bash tests/fm-crew-state.test.sh` passes in full ("all fm-crew-state tests passed", the new test included). `shellcheck` on the three changed shell files reports only the existing info-level SC1091 "not following sourced file" notes. No end-to-end run against a live no-mistakes daemon was possible here. I checked the change with the crew-state test harness and its fake `no-mistakes` CLI

---------

Co-authored-by: mehulbhagwani <mehulbhagwani@users.noreply.github.com>
Co-authored-by: Pitch Perfekt Collective <pitchperfekt@Pitchs-Mac-Studio.local>
… full-ci label

Outside pull requests run only the coverage guard and repo invariants until the full-ci label is added; lint and every test lane wait.

A new Full suite check fails naming the full-ci label until it is added, and fails whenever a lane fails or is cancelled.

The label starts the full suite, and later pushes run it again; main pushes, own branches and write-access authors run everything as before.

Other labels run no job at all: their skipped checks take a 'Not run for label - ' prefix, so they cannot hide an earlier failure or a later pass.

Close main pushes keep only the newest run; CONTRIBUTING.md explains the label, and the full-ci label now exists.
…atch resolution (kunchenguid#7664)

* feat(bin): add opt-in systemone and openai providers to typed dispatch resolution

Extend the FM_DISPATCH_PROVIDER/FM_DISPATCH_MODEL seam with a generic
systemone provider (any System One-compatible host at FM_DISPATCH_BASE_URL,
optional FM_DISPATCH_API_KEY) and an openai provider that maps the Choice
request to OpenAI's Decisions API and its answer back. No provider set keeps
the original off line; the never-send check runs on the endpoint and the final
per-provider body. Live provider captures are recorded as pending.

Refs kunchenguid#7643, builds on kunchenguid#7149.

* docs(verification): record the live OpenAI Decisions capture for dispatch resolution

Add a dated 2026-10-23 section for the openai provider: 25 hand-labeled
synthetic briefs against gpt-6-luna, outcome and latency tables, the usage
and header fields actually returned, a calibration note, and one verbatim
request and response pair. The systemone live check stays pending.

Refs kunchenguid#7643.

* docs(verification): record the live Clef capture for the systemone provider

Replace the pending systemone section with a dated 2026-10-23 run against
Cloudflare Workers AI's @cf/cloudflare/clef, reached through a local
test-only relay because Workers AI serves System One at /ai/run/<model> in
a {result, success} envelope. Adds the outcome and latency tables, a
calibration note compared with the gpt-6-luna run, and one verbatim request
and envelope pair. A direct Workers AI option stays out of scope.

Refs kunchenguid#7643.
…henguid#7941)

* feat(agy): promote Antigravity CLI to full primary and secondmate harness

Add tracked .agents/hooks.json, bin/fm-sessionstart-agy.sh, bin/fm-turnend-guard-agy.sh, PreToolUse --agy seatbelt flags, session-lock agy ancestry, autoarm supervision model, and --secondmate agy workspace trust pre-registration and spawn support.

* no-mistakes(review): Fix agy Stop-hook park, hooks.json shape, and live coverage

* no-mistakes(review): Unbound agy Stop park, raise ceiling, prove live deny

* no-mistakes(review): Correct agy verification doc park-bound wording

* no-mistakes(document): Document agy as verified primary and secondmate harness

* no-mistakes(ci): I fixed all three selected findings, and every check I ran passed, including the live agy end-to-end run. **ci-1 (`tests/fm-agy-primary-live-e2e.test.sh`):** The invariant is that the live Stop-hook test passes only if a real watcher starts and its wake reaches a follow-up turn. The old test passed as soon as `.agy-park-owner` existed. I replaced the Stop-hook park block with the one the user specified. It waits until both `.agy-park-owner` and `.last-watcher-beat` exist, wakes the watcher with a `done:` status line, and asserts that the park owner was recorded, the watcher started, and `DONE` appears in the agy output. **ci-2 (agy away daemon):** The invariant is that `/afk` must not launch a separate away daemon on any primary whose home runs the supervision host. The agy Stop-hook guard already runs the host when it is enabled, but `fm_afk_launch_host_primary` left agy out. I added `agy` there, which is the one shared check all three callers use. I also added agy to: - the header comment in `bin/fm-afk-launch.sh`; - the three harness loops and the pass message in `tests/fm-afk-launch.test.sh`; - the primary lists and counts in `docs/supervision-host.md`, the matching lines in its engine-selection section, and a new Antigravity (`agy`) row in its Arm owners table; - the header comment in `bin/fm-supervision-instructions.sh`. **user-1 (`.agents/hooks.json`):** The invariant is that each hook command stays one script under Windows `cmd /c` and exits 0 when `sh` is missing. All 5 commands now use `sh -c "..." || cd .`. This does not change behaviour on Linux or macOS, because the `--agy` hooks report deny or continue as JSON on stdout and exit 0. The file keeps its live-verified `{"firstmate":{...}}` shape, matchers and 28800s Stop timeout. **Verification:** - `tests/fm-agy-primary.test.sh` passes, including running the SessionStart hook command from both `.agents/` and the repo root. - `tests/fm-afk-launch.test.sh` has no failures, and the new agy check passes. Its rex end-to-end cases were skipped because the rex CLI is not installed. - `tests/fm-supervision-instructions.test.sh` has no failures. - `bin/fm-lint.sh` and `bin/fm-doc-audience-check.sh` pass. - `FM_AGY_PRIMARY_LIVE=1 tests/fm-agy-primary-live-e2e.test.sh` printed all four `ok` lines and no failures, including "Stop hook parks on a real watcher and delivers its wake to a follow-up turn". The output went through `tail`, so the exit code shown is `tail`'s, not the test script's
…kunchenguid#7894)

* feat(dispatch): add shared_quota home opt-out from quota-based ranking

A home whose Claude and other provider usage draws on one combined AI
budget has no meaningful per-provider quota rows, so spendPriority
ranking and runway gates pick the wrong candidate. An optional
top-level "shared_quota": true in config/crew-dispatch.json now opts
the home out: the typed resolver makes no quota-axi call, applies no
quota floor or runway gate, ranks nothing by spendPriority, and
resolves the matched rule's use profiles in declared order, printing
every candidate line with quota ranking off by home declaration.
Firstmate's quota-array-dispatch intake honors the same declaration.
Without the setting, behavior is unchanged. Non-boolean values are
actionable configuration errors in both the resolver and bootstrap.

* test(dispatch): cover shared-quota bypass of unmet rule and profile floors

The shared_quota opt-out test picked a rule with no quota floor, so the
separate rule-floor and profile-floor bypass paths stayed untested.
Two cases now run against the same fixture: a rule whose model:fable
floor (20%) exceeds the snapshot's 15%, and a profile whose all_models
floor (50%) exceeds codex's 31%, each asserting the declared-order pick
still wins with no quota-axi invocation.

* no-mistakes(review): Narrow shared-quota skill opt-out to quota-only gates

* no-mistakes(review): Document preferred_provider under shared_quota opt-out and test it

* no-mistakes(document): Document shared_quota in schema example and verification inventory
…henguid#7958)

* fix(bin): distinguish recycled reaper PIDs in tombstone cleanup

* no-mistakes(ci): ci-2 fixed in tests/fm-watcher-lock.test.sh (test_lock_tombstone_tracks_reaper_start_time). Invariant: no fixture child may outlive a SIGKILLed runner without a bound. (1) The paused reaper's wait loop now also stops when FM_TEST_SUITE_PID is no longer alive or when SECONDS reaches FM_TEST_STUB_MAX_BLOCK_SECONDS. (2) The one sibling child in the same test, the unrelated `sleep 300`, now sleeps for FM_TEST_STUB_MAX_BLOCK_SECONDS so it uses the same bound. Verification, run serially: bash tests/fm-watcher-lock.test.sh. The new test passed on 4 of 4 runs. Red-before still holds: with the base bin/fm-wake-lib.sh from c9da552, the test fails with 'not ok - recycled reaper pid kept an abandoned tombstone alive'. Separately, the known stand-down flake ('the replaced watcher did not stand down on its own', watch-legacy-local-time-identity) failed on every run on this host and also fails on base c9da552 without this change, so the test file still exits 1; left unfixed as instructed. ci-1 (Full suite waiting on the full-ci maintainer label) was ignored per the user's decision
* feat: allow omp as a remote secondmate harness

* test: cover omp on the remote secondmate gates

* fix: retire a remote secondmate whose busy state left with its home

* docs: record omp remote retirement evidence

* review: Refresh pinned remote doctor bootstrap checksum
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ed25b0e8-6d49-4ac2-bb29-bf1b5ccdc687
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@trillium
trillium force-pushed the fix/spawn-refuse-live-sibling-worktree branch from 1ae1ba5 to b129e04 Compare October 30, 2026 04:55
trillium and others added 4 commits October 29, 2026 21:55
A pooled worktree handed back by treehouse (or created by orca) can still be owned by a sibling task whose agent endpoint is alive. Launching a second worker into it tangles two tasks in one checkout. Before the isolation check, validate_spawn_worktree now scans this home's state/*.meta for another task that records the same worktree or home and whose endpoint is positively alive, and refuses the spawn. Only a positively alive endpoint vetoes, so a stale record never blocks a fresh slot, and the calling task's own record never counts against it (relaunch adopts it).
@trillium
trillium force-pushed the fix/spawn-refuse-live-sibling-worktree branch from b129e04 to 28cf1ef Compare October 30, 2026 05:10
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.