Skip to content

fix(spawn): lease a task's pool worktree so it is never reassigned - #10

Merged
talktejas merged 10 commits into
mainfrom
fm/spawn-pool-claim-r2
Sep 20, 2026
Merged

talktejas merged 10 commits into
mainfrom
fm/spawn-pool-claim-r2

Conversation

@talktejas

@talktejas talktejas commented Sep 20, 2026 •

Copy link
Copy Markdown
Owner

Intent

This branch replaces PR 4 #4, which cannot be revalidated: its branch history was rewritten, so no-mistakes refuses to reconcile that branch's private mirror record. Same content, clean branch from current main.

The captain approved merging both firstmate pull requests, but neither can merge as it stands:

  • PR 3 feat(spawn): add a per-task --base branch for ship and scout spawns #3 (feat(spawn): add a per-task --base branch for ship and scout spawns) conflicts with main, and its only failing check is "PR must be raised via no-mistakes".
  • PR 4 fix(spawn): claim a task's pool worktree so it is never reassigned under a live task #4 (fix(spawn): claim a task's pool worktree so it is never reassigned under a live task) conflicts with main, and fails "PR must be raised via no-mistakes", "Behavior portable serial 2" and "Stock macOS Bash snapshot compatibility".
    Both fixes matter daily: without the first, every worker starts from the project's default branch and the captain's integration-branch rule depends on hand-patched instructions; without the second, spawns fail with "pooled worktree not clean" because a live worker's copy is handed to a new task.

This branch is PR 4. Its own ask, which the approval above is an approval of: on 2026-08-19 a spawn was handed a pooled working copy that another task's own record still named as its live working copy. The pool reset that directory to a detached HEAD, evicting a paused task from the copy holding its work; nothing was lost only because that task had already pushed its branch. The cause is what the pool can see: Treehouse counts a slot as in use only while a process is running inside it, so a task whose agent has exited - paused, crashed, or simply between turns - leaves its own working copy looking free, and the next allocation takes it. Stop the pool offering that copy at all, rather than only refusing it after it has already been reset: have the spawn take the pool's own durable claim itself instead of relying on the pane's subshell to hold the slot, then move the pane into the claimed copy. Keep a named refusal as a second line of defence for the one copy a claim cannot protect - a task spawned before claims existed, holding its slot with nothing but its processes - and have that refusal name the task that holds it. Every claim must have a stated release path: ordinary teardown, aborted-spawn cleanup, and a self-identifying holder label recorded with the task so a claim outliving its task is releasable with one command.

Captain's ruling on conflicts, given while this rebase was under way: upstream (kunchenguid/firstmate) code wins wherever upstream has something; where it has nothing, resolve on your own. Upstream has NOT landed either fix - the per-task base branch is still open issue kunchenguid#3411 and the pool-reassignment bug is open issues kunchenguid#1441 and kunchenguid#1573, with several competing unmerged PRs (kunchenguid#4828, kunchenguid#4071, kunchenguid#4909, kunchenguid#4468, kunchenguid#3896). So: when a conflict hunk is upstream's own change to shared code, take upstream's version and re-apply our behaviour on top of it; only our two features are ours to keep. Do not adopt an unmerged upstream PR.

What Changed

  • bin/fm-spawn.sh now takes the pool worktree itself via treehouse get --lease --lease-holder fm:<task-id>@<state-dir> (bounded by the new FM_TREEHOUSE_GET_TIMEOUT, default 60s) instead of relying on the pane's treehouse get subshell to hold the slot, then sends (cd -- '<wt>' && exec $SHELL) into the pane and polls until it is observed in that leased copy; the holder label is recorded as worktree_lease= in the task record and carried across relaunch. Release paths are aborted-spawn cleanup, failed-dispatch rollback (which warns with the exact treehouse return --force --if-lease-holder command), and ordinary bin/fm-teardown.sh return.
  • Added a named refusal for a leased copy that an existing lease-less task record still names as its working copy: the spawn exits naming that task, keeps the lease so the slot is quarantined rather than re-offered, and prints the reconcile and release commands. bin/fm-bootstrap.sh now gates on get --lease, get --lease-holder and return --if-lease-holder, and bin/fm-install-treehouse.sh pins Treehouse v2.1.0 with updated checksums.
  • Generalized fm_tasks_axi/fm_tasks_axi_timeout_expired into fm_run_bounded/fm_run_bounded_timed_out in bin/fm-backlog-transition-lib.sh (tasks-axi keeps a thin wrapper), refreshed backend and docs comments that described the old pane-driven acquisition, documented worktree_lease= and FM_TREEHOUSE_GET_TIMEOUT in docs/configuration.md, and added tests/fm-spawn-worktree-lease.test.sh plus a shared treehouse lease stub in tests/lib.sh used across the updated suites.

🤖 Generated with Claude Code

Risk Assessment

⚠️ Medium: The change replaces the worktree-acquisition mechanism for every ship/scout spawn across four backends (~250 lines in the core spawn path), but the lease lifecycle and abort orderings trace correctly, the CI pin and verification record check out against the real treehouse v2.1.0 artifacts and behavior, and the only finding is a cosmetic quoting issue in diagnostic text.

Testing

I drove bin/fm-spawn.sh against a real treehouse v2.1.0 pool rather than only stubs. On the base commit a paused task's idle copy was handed to the next spawn and reset to a detached HEAD, destroying its WIP - the reported incident; on this branch the same scenario gives the new spawn a different copy and leaves the paused worker's branch and uncommitted work intact. Every release path the intent requires was exercised live: ordinary teardown-shaped treehouse return --force frees the durable claim, an aborted spawn returns it holder-matched with no record left behind, the refusal's printed project-scoped treehouse return --force --if-lease-holder command actually releases the quarantined slot when pasted from an unrelated working directory while a wrong holder is refused, and a relaunch carries the original claim forward exactly once. The refusal itself names the task whose record holds the copy and keeps the slot claimed so the pool does not loop. For the CI pin I ran bin/fm-install-treehouse.sh for real: it downloaded and SHA-256-verified the v2.1.0 artifact, whose help output advertises get --lease, get --lease-holder and return --if-lease-holder; bootstrap is silent with that binary on PATH and still prints "MISSING: treehouse" for a v2.0.1-shaped build that lacks the holder-matched return, so the capability gate was not weakened. Suite-level: fm-spawn-worktree-lease (including its real-pool cases), fm-spawn-dispatch-profile (the target commit's GNU -k timeout stub), the three formerly red stale-stub suites (fm-startup-memory-budget, fm-session-start, fm-secondmate-sync), fm-agy-harness, fm-bootstrap, fm-x-mode, fm-backend, fm-spawn-pool-base-freshen and fm-spawn-worktree-settle all pass. fm-secondmate-liveness fails one Herdr classifier unit case on this host because a real herdr is installed and reports the session stopped; that function is byte-identical at the base commit, so it is environmental, not a regression. I stopped the remaining stub-swap-only suites (captain-hold-lifecycle and later) after captain-hold-lifecycle ran far past its usual duration on this loaded host; they carry no behaviour from this change beyond the stub swap already validated elsewhere, and CI owns broad regression. Evidence is CLI transcripts and real pool JSON state; the change has no UI surface, so no screenshots apply.

  • Live validation: ✅ go - 12 of 12 scenarios driven live against the product
Scenario Result Live Evidence
A paused task's pool copy is never handed to the next spawn (real treehouse pool) ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/real-pool-spawn-does-not-reassign-live-copy.txt - beta-2 got slot 2 while alpha-1's leased slot 1 kept branch alpha-feature and its uncommitt…
Without the fix the same run reassigns the live copy and resets it (regression reproduces at base commit) ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/pre-fix-reproduces-reassignment.txt - REPRODUCED: beta-2 handed alpha-1's copy, HEAD detached, WIP gone
An aborted spawn gives its claim straight back, holder-matched, with no task record ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/aborted-spawn-returns-claim-real-pool.txt - pool status back to available, no .meta published
A copy a live pre-claim task's record names is refused by name, stays quarantined, and the spawn can be re-run ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/refusal-names-holder-and-recovery-command-works.txt and spawn-claim-transcript.txt - refusal names oldtimer-9/paused-task-w9 and the re-run g…
The printed recovery command releases the claim from any working directory and refuses a holder that does not hold it ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/recovery-command-live.txt - run from $HOME: wrong holder fails with 'lease precondition failed', correct holder returns the slot to the pool
Ordinary teardown frees a durable claim ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/real-pool-spawn-does-not-reassign-live-copy.txt step 5 and recovery-command-live.txt - treehouse return --force &lt;slot&gt; flips status leased…
A relaunch carries the task's original claim forward exactly once ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/relaunch-carries-claim-forward.txt
A stalled pool allocation refuses within its bound and leaves no project lockdir behind ✅ pass live bash tests/fm-spawn-worktree-lease.test.sh - 'a stalled allocation refuses within its bound and gives the project lock back' (evidence/lease-suite-run.txt)
The pinned CI treehouse installs, verifies its checksum, and advertises the three gated flags ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/ci-installer-v2.1.0.txt - real v2.1.0 download, installer exit 0, --lease/--lease-holder/--if-lease-holder advertised
Bootstrap's capability gate stays silent for the pinned build and still refuses one without holder-matched release ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/bootstrap-lease-gate.txt - empty output for v2.1.0, 'MISSING: treehouse' for a v2.0.1-shaped build
Spawns in the dispatch-profile suite survive the bounded timeout -k &lt;bound&gt; &lt;bound&gt; invocation (target commit) ✅ pass live bash tests/fm-spawn-dispatch-profile.test.sh - 59 checks pass, no 'could not lease a worktree' aborts (evidence/lease-suite-run.txt)
Suites that assert bootstrap silence are quiet again with the flag-modelling treehouse stub ✅ pass live bash tests/fm-startup-memory-budget.test.sh, tests/fm-session-start.test.sh, tests/fm-secondmate-sync.test.sh, tests/fm-agy-harness.test.sh - all exit 0, no 'MISSING: treehouse'
Evidence: Post-fix: real pool never reassigns a paused task's copy

Source: Post-fix: real pool never reassigns a paused task's copy

=== treehouse: v2.1.0 (real binary: ~/.local/bin/treehouse) ===

=== 1. spawn task alpha-1 ===
  warning: /tmp/fm-real-pool.yurAva/home/data/alpha-1/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
  🌳 Setting up worktree...
  🌳 Leased worktree at /tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/1/project. Run 'treehouse return /tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/1/project' to release it.
  spawned alpha-1 harness=codex kind=ship mode=no-mistakes yolo=off window=firstmate:fm-alpha-1 worktree=/tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/1/project
  alpha-1 worktree : /tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/1/project
  alpha-1 claim    : worktree_lease=fm:alpha-1@/tmp/fm-real-pool.yurAva/home/state

=== 2. alpha-1's worker does real work, then its agent EXITS (the paused task) ===
  branch in alpha's copy : alpha-feature
  uncommitted work       : alpha work in progress
  processes in the slot  : 1 slot(s) with NO process inside
  pool sees:
    [{"name":"1","path":"/tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/1/project","status":"leased","lease_id":"29de33c1d36e739848efc07c49fac128","lease_holder":"fm:alpha-1@/tmp/fm-real-pool.yurAva/home/state","leased_at":"2026-09-20T18:11:10.272219351+07:00","processes":[]}]

=== 3. a NEW spawn (beta-2) asks the pool for a worktree ===
  warning: /tmp/fm-real-pool.yurAva/home/data/beta-2/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
  A new version of treehouse is available: v2.1.0 → v2.3.0
  Run "treehouse update" to update
  
  🌳 Setting up worktree...
  🌳 Leased worktree at /tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/2/project. Run 'treehouse return /tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/2/project' to release it.
  spawned beta-2 harness=codex kind=ship mode=no-mistakes yolo=off window=firstmate:fm-beta-2 worktree=/tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/2/project
  beta-2 worktree  : /tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/2/project

=== 4. verdict ===
  PASS: beta-2 got a DIFFERENT copy (/tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/2/project) - alpha-1's copy was never offered
  PASS: alpha-1's copy still on branch alpha-feature with its uncommitted work intact (not reset to a detached HEAD)

=== 5. ordinary teardown of alpha-1 releases its claim ===
  A new version of treehouse is available: v2.1.0 → v2.3.0
  Run "treehouse update" to update
  
  🌳 Worktree returned to pool.
    [{"name":"1","path":"/tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/1/project","status":"available","lease_id":"","lease_holder":"","leased_at":null,"processes":[]},{"name":"2","path":"/tmp/fm-real-pool.yurAva/project/.treehouse/project-148de5/2/project","status":"leased","lease_id":"c596469460b72dd00e9d2b26f28fe59a","lease_holder":"fm:beta-2@/tmp/fm-real-pool.yurAva/home/state","leased_at":"2026-09-20T18:11:11.623639589+07:00","processes":[]}]
  PASS: teardown released alpha-1's claim; slot is back in the pool
Evidence: Pre-fix reproduction of the 2026-08-19 reassignment (base commit)

Source: Pre-fix reproduction of the 2026-08-19 reassignment (base commit)

=== PRE-FIX bin/fm-spawn.sh (base commit e80a29a), real treehouse v2.1.0 ===

=== 1. spawn alpha-1 ===
  warning: /tmp/fm-base-repro.2pSark/home/data/alpha-1/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 alpha-1 harness=codex kind=ship mode=no-mistakes yolo=off window=firstmate:fm-alpha-1 worktree=/tmp/fm-base-repro.2pSark/project/.treehouse/project-2ae262/1/project
  alpha-1 worktree : /tmp/fm-base-repro.2pSark/project/.treehouse/project-2ae262/1/project
  alpha-1 claim    : (none - pre-fix record carries no pool claim)

=== 2. alpha-1 does work; its agent exits (paused task, nothing running in the copy) ===
  branch: alpha-feature, WIP present: yes
  pool sees:
    [{"name":"1","path":"/tmp/fm-base-repro.2pSark/project/.treehouse/project-2ae262/1/project","status":"available","lease_id":"","lease_holder":"","leased_at":null,"processes":[]}]

=== 3. a new spawn (beta-2) asks the pool ===
  warning: /tmp/fm-base-repro.2pSark/home/data/beta-2/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 beta-2 harness=codex kind=ship mode=no-mistakes yolo=off window=firstmate:fm-beta-2 worktree=/tmp/fm-base-repro.2pSark/project/.treehouse/project-2ae262/1/project
  beta-2 worktree  : /tmp/fm-base-repro.2pSark/project/.treehouse/project-2ae262/1/project

=== 4. verdict (pre-fix) ===
  REPRODUCED: beta-2 was handed alpha-1's live copy - the 2026-08-19 incident
  alpha-1's copy now: HEAD=HEAD detached=YES WIP present=NO
Evidence: Spawn CLI transcript: claim taken, refusal by name, aborted-spawn release

Source: Spawn CLI transcript: claim taken, refusal by name, aborted-spawn release

=== 1. Ordinary spawn: takes the pool's durable claim, labelled with its own record
spawn exit: 0
-- task record:
worktree=/tmp/fm-spawn-lease-transcript.CqT508/demo-claim/slot
worktree_lease=fm:demo-claim@/tmp/fm-spawn-lease-transcript.CqT508/demo-claim/home/state
-- pool calls the spawn made:
treehouse get --lease --lease-holder fm:demo-claim@/tmp/fm-spawn-lease-transcript.CqT508/demo-claim/home/state

=== 2. Second line of defence: pool offers a copy a live pre-claim task's record names
spawn exit: 1
warning: /tmp/fm-spawn-lease-transcript.CqT508/demo-conflict/home/data/demo-conflict/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: the pool offered '/tmp/fm-spawn-lease-transcript.CqT508/demo-conflict/slot', which task paused-task-w9's own record still names as its working copy; refusing to launch task demo-conflict into it and reset another worker's work
That copy is now held under 'fm:demo-conflict@/tmp/fm-spawn-lease-transcript.CqT508/demo-conflict/home/state' so it is not offered again; reconcile whichever record is wrong (bin/fm-crew-state.sh paused-task-w9), then release it with: (cd '/tmp/fm-spawn-lease-transcript.CqT508/demo-conflict/project' && treehouse return --force --if-lease-holder 'fm:demo-conflict@/tmp/fm-spawn-lease-transcript.CqT508/demo-conflict/home/state' '/tmp/fm-spawn-lease-transcript.CqT508/demo-conflict/slot')
Re-running this spawn is safe: it is given a different copy.

=== 3. Aborted spawn hands the claim straight back (holder-matched)
spawn exit: 1
warning: /tmp/fm-spawn-lease-transcript.CqT508/demo-abort/home/data/demo-abort/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: window firstmate:fm-demo-abort did not enter its leased worktree '/tmp/fm-spawn-lease-transcript.CqT508/demo-abort/slot' within 60s (last seen '/tmp/fm-spawn-lease-transcript.CqT508/demo-abort/project'; spawning project '/tmp/fm-spawn-lease-transcript.CqT508/demo-abort/project'); inspect window firstmate:fm-demo-abort
-- pool calls:
treehouse get --lease --lease-holder fm:demo-abort@/tmp/fm-spawn-lease-transcript.CqT508/demo-abort/home/state
treehouse return --force --if-lease-holder fm:demo-abort@/tmp/fm-spawn-lease-transcript.CqT508/demo-abort/home/state /tmp/fm-spawn-lease-transcript.CqT508/demo-abort/slot
Evidence: Printed recovery command run live from an unrelated cwd

Source: Printed recovery command run live from an unrelated cwd

\### Ordinary teardown path (treehouse return --force <slot>) frees a durable claim - real treehouse v2.1.0
leased slot: /tmp/fm-recovery-cmd-check/repo/.treehouse/repo-ea1569/1/repo
[{"name":"1","path":"/tmp/fm-recovery-cmd-check/repo/.treehouse/repo-ea1569/1/repo","status":"leased","lease_id":"7e786d71768652283211f190212edd19","lease_holder":"fm:recovery-demo@state","leased_at":"2026-09-20T18:07:17.140937317+07:00","processes":[]}]

\### Printed recovery command run from an unrelated cwd ($HOME), project-scoped form
cwd: ~
$ (cd '/tmp/fm-recovery-cmd-check/repo' && treehouse return --force --if-lease-holder 'fm:someone-else@state' '/tmp/fm-recovery-cmd-check/repo/.treehouse/repo-ea1569/1/repo')   # wrong holder

failed to return worktree: lease precondition failed: lease holder does not match worktree /tmp/fm-recovery-cmd-check/repo/.treehouse/repo-ea1569/1/repo
exit=1
$ (cd '/tmp/fm-recovery-cmd-check/repo' && treehouse return --force --if-lease-holder 'fm:recovery-demo@state' '/tmp/fm-recovery-cmd-check/repo/.treehouse/repo-ea1569/1/repo')   # holder that took it

🌳 Worktree returned to pool.
exit=0
-- pool after:
[{"name":"1","path":"/tmp/fm-recovery-cmd-check/repo/.treehouse/repo-ea1569/1/repo","status":"available","lease_id":"","lease_holder":"","leased_at":null,"processes":[]}]
Evidence: Aborted spawn returns its claim (real pool state)

Source: Aborted spawn returns its claim (real pool state)

=== spawn whose pane never enters the leased copy (real treehouse v2.1.0) ===
  warning: /tmp/fm-abort-release.gYdwCv/home/data/stuck-7/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
  🌳 Setting up worktree...
  🌳 Leased worktree at /tmp/fm-abort-release.gYdwCv/project/.treehouse/project-4f0a48/1/project. Run 'treehouse return /tmp/fm-abort-release.gYdwCv/project/.treehouse/project-4f0a48/1/project' to release it.
  error: window firstmate:fm-stuck-7 did not enter its leased worktree '/tmp/fm-abort-release.gYdwCv/project/.treehouse/project-4f0a48/1/project' within 60s (last seen 'none'; spawning project '/tmp/fm-abort-release.gYdwCv/project'); inspect window firstmate:fm-stuck-7
  spawn exit: 1
  task record published: no
=== pool state after the abort ===
  [{"name":"1","path":"/tmp/fm-abort-release.gYdwCv/project/.treehouse/project-4f0a48/1/project","status":"available","lease_id":"","lease_holder":"","leased_at":null,"processes":[]}]
  PASS: the aborted spawn gave the claim straight back - no record, no orphaned claim
Evidence: Refusal names the holder and the quarantine is releasable

Source: Refusal names the holder and the quarantine is releasable

=== 1. legacy task 'oldtimer-9' holds a pool copy with nothing but its processes (spawned before claims existed) ===
  oldtimer-9's record still names : /tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/1/project
  its record carries a claim      : 0 line(s)
  the pool sees that copy as      : "status":"available"

=== 2. a new spawn is offered that very copy ===
  exit: 1
  warning: /tmp/fm-refusal-recovery.8MziIC/home/data/newcomer-10/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
  A new version of treehouse is available: v2.1.0 → v2.3.0
  Run "treehouse update" to update
  
  🌳 Setting up worktree...
  🌳 Leased worktree at /tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/1/project. Run 'treehouse return /tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/1/project' to release it.
  error: the pool offered '/tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/1/project', which task oldtimer-9's own record still names as its working copy; refusing to launch task newcomer-10 into it and reset another worker's work
  That copy is now held under 'fm:newcomer-10@/tmp/fm-refusal-recovery.8MziIC/home/state' so it is not offered again; reconcile whichever record is wrong (bin/fm-crew-state.sh oldtimer-9), then release it with: (cd '/tmp/fm-refusal-recovery.8MziIC/project' && treehouse return --force --if-lease-holder 'fm:newcomer-10@/tmp/fm-refusal-recovery.8MziIC/home/state' '/tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/1/project')
  Re-running this spawn is safe: it is given a different copy.

=== 2b. adversarial: the refusal claims re-running is safe - re-run it right now, while the bad copy is still quarantined ===
  spawned newcomer-10 harness=codex kind=ship mode=no-mistakes yolo=off window=firstmate:fm-newcomer-10 worktree=/tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/2/project
  PASS: the re-run was given a different copy (/tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/2/project) and launched - the pool did not deadlock on the quarantined slot

=== 3. the operator pastes the printed recovery command VERBATIM, from an unrelated working directory ===
  cwd: /
  $ (cd '/tmp/fm-refusal-recovery.8MziIC/project' && treehouse return --force --if-lease-holder 'fm:newcomer-10@/tmp/fm-refusal-recovery.8MziIC/home/state' '/tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/1/project')
  A new version of treehouse is available: v2.1.0 → v2.3.0
  Run "treehouse update" to update
  
  🌳 Worktree returned to pool.
  recovery exit: 0

=== 4. after recovery ===
  [{"name":"1","path":"/tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/1/project","status":"available","lease_id":"","lease_holder":"","leased_at":null,"processes":[]},{"name":"2","path":"/tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/2/project","status":"leased","lease_id":"a23014e40baf365abbe946a3ec5dea2b","lease_holder":"fm:newcomer-10@/tmp/fm-refusal-recovery.8MziIC/home/state","leased_at":"2026-09-20T18:11:30.394581889+07:00","processes":[]}]
  quarantined slot now: [{"name":"1","path":"/tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/1/project","status":"available","lease_id":"","lease_holder":"","leased_at":null,"processes":[]},{"name":"2","path":"/tmp/fm-refusal-recovery.8MziIC/project/.treehouse/project-9e21c8/2/project","status":"leased","lease_id":"a23014e40baf365abbe946a3ec5dea2b","lease_holder":"fm:newcomer-10@/tmp/fm-refusal-recovery.8MziIC/home/state","leased_at":"2026-09-20T18:11:30.394581889+07:00","processes":[]}]
  PASS: the pasted command released the quarantine claim and returned that slot to the pool
Evidence: Relaunch carries the claim forward

Source: Relaunch carries the claim forward

=== record before the relaunch ===
  worktree=/tmp/fm-relaunch-claim2.s1E5fq/case/wt
  worktree_lease=fm:relaunch-claim-1@/tmp/fm-relaunch-claim2.s1E5fq/case/home/state

=== fm-spawn.sh relaunch-claim-1 --relaunch ===
  warning: /tmp/fm-relaunch-claim2.s1E5fq/case/home/data/relaunch-claim-1/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 relaunch-claim-1 harness=claude kind=ship mode=no-mistakes yolo=off window=fmses:fm-relaunch-claim-1 worktree=/tmp/fm-relaunch-claim2.s1E5fq/case/wt

=== record after the relaunch ===
  worktree=/tmp/fm-relaunch-claim2.s1E5fq/case/wt
  worktree_lease=fm:relaunch-claim-1@/tmp/fm-relaunch-claim2.s1E5fq/case/home/state
  PASS: the relaunch carried the task's original pool claim forward, exactly once
Evidence: CI pin: real v2.1.0 install, checksum, advertised flags

Source: CI pin: real v2.1.0 install, checksum, advertised flags

\### bin/fm-install-treehouse.sh <dir> - real download + SHA-256 verification (linux-amd64)
fm-install-treehouse.sh: downloading treehouse-v2.1.0-linux-amd64.tar.gz from https://github.com/kunchenguid/treehouse/releases/download/v2.1.0/treehouse-v2.1.0-linux-amd64.tar.gz
fm-install-treehouse.sh: installed treehouse v2.1.0 to /tmp/fm-th-pin/treehouse
v2.1.0
installer exit=0

\### advertised flag surface of the installed pin
v2.1.0
      --lease                 Durably lease a worktree without opening a subshell; print only its path to stdout
      --lease-holder string   Optional label recorded as the lease holder (defaults to $TREEHOUSE_LEASE_HOLDER)
      --if-lease-holder string   Return only if the current lease has this holder
Evidence: Bootstrap lease gate: silent on v2.1.0, MISSING on a v2.0.1-shaped build

Source: Bootstrap lease gate: silent on v2.1.0, MISSING on a v2.0.1-shaped build

\### A. real pinned treehouse (v2.1.0, installed by fm-install-treehouse.sh)
v2.1.0
bootstrap output: []
RESULT: PASS - pinned build satisfies the lease gate

\### B. v2.0.1-shaped build: get --lease/--lease-holder, no return --if-lease-holder
bootstrap output: [MISSING: treehouse (install: curl -fsSL https://kunchenguid.github.io/treehouse/install.sh | sh)]
RESULT: PASS - gate refuses a build with no holder-matched release
Evidence: Lease and dispatch-profile suite runs

Source: Lease and dispatch-profile suite runs

\### tests/fm-spawn-worktree-lease.test.sh (real treehouse v2.1.0 on PATH)
ok - a spawn claims its pool slot with a lease labelled by the owning task record
ok - a stalled allocation refuses within its bound and gives the project lock back
ok - an aborted spawn returns its claim, matched on the holder that took it
ok - a copy another task record claims is refused by name, and stays claimed
ok - a record holding its own claim does not block a spawn the pool allocated
ok - the real pool does not hand out a leased slot that has no process inside it
ok - a claim is released only by the holder that took it, and the slot returns to the pool
# all fm-spawn-worktree-lease tests passed

\### tests/fm-spawn-dispatch-profile.test.sh (GNU -k timeout stub, target commit)
ok - a persistent claude secondmate keeps its supervisor contract without a task-worker authority overlay
ok - a claude crewmate launch carries the attribution-off policy in its own settings
ok - a claude secondmate launch carries the attribution-off policy too
ok - active crew-dispatch profile does not block secondmate launches
# all fm-spawn-dispatch-profile tests passed
- Outcome: ⚠️ 1 info across 2 runs (59m53s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • 🚨 tests/fm-agy-harness.test.sh:547 - fm-spawn.sh no longer learns the worktree from the pane's cwd; it now reads it from treehouse get --lease stdout (bin/fm-spawn.sh:3748) and refuses with "treehouse get --lease reported no worktree" when that is empty (bin/fm-spawn.sh:3760). make_agy_fakebin still installs a bare exit-0 treehouse (fm_fake_exit0 &#34;$fakebin&#34; treehouse gh-axi gh), which prints nothing, so every real ship spawn this suite runs through "$SPAWN" (tests/fm-agy-harness.test.sh:612) now aborts before the pane is touched, regardless of FM_FAKE_PANE_PATH. Other spawn suites were converted in this branch (fixtures.sh's fm_test_make_spawn_fakebin now installs fm_test_fake_treehouse_lease, whose get --lease falls back to FM_FAKE_PANE_PATH); this one was missed. Remedy: replace the exit-0 treehouse here with fm_test_fake_treehouse_lease, the same stub the rest of the suite set now uses.
  • 🚨 tests/fm-startup-memory-budget.test.sh:38 - Commit 386a678 widened bin/fm-bootstrap.sh's treehouse_supports_lease from one advertised flag to three (get --lease, get --lease-holder, return --if-lease-holder, bin/fm-bootstrap.sh:950), but three suites still install a stub whose get --help prints only 'Usage: treehouse get [--lease]' and whose return --help prints nothing. I ran the new probe against that exact stub: --lease passes, --lease-holder and --if-lease-holder both fail, so bootstrap emits "MISSING: treehouse" for a tmux/herdr backend (bin/fm-bootstrap.sh:1499). That breaks tests/fm-startup-memory-budget.test.sh:104 ("default materialization should stay quiet"), tests/fm-secondmate-sync.test.sh:607 ("idempotent retry should converge to silence", stub at :362), and tests/fm-session-start.test.sh:1084 (assert_not_contains "MISSING: treehouse", stub at :90) - the same three failures the user already paid a CI-fix budget to root-cause. tests/fm-x-mode.test.sh:809 and tests/fm-secondmate-liveness.test.sh:225 carry the same stale stub but only use assert_contains/assert_not_contains on other lines, so they survive; update them anyway for consistency. Remedy per the recorded decision: present fm_test_fake_treehouse_lease in these suites rather than weakening the probe or the silence assertions.
  • ℹ️ bin/fm-spawn.sh:3788 - The named second-line refusal skips any sibling record that carries worktree_lease= ([ -z &#34;$(fm_meta_get &#34;$spawn_other_meta&#34; worktree_lease)&#34; ] || continue), on the reasoning that the pool would not have offered a copy whose record holds a claim. If a claim is force-released while its record is still live - the recovery commands this change prints all use treehouse return --force --if-lease-holder, and teardown that returns the slot but dies before removing the record leaves the same shape - the next spawn is handed that copy and launches into it, which is the 2026-08-19 incident. Noting the tradeoff only: the user intent explicitly scopes the refusal to "the one copy a claim cannot protect - a task spawned before claims existed", so always refusing on any record that names the offered copy (which would also be the simpler single rule) would widen the change beyond its stated scope. No action needed unless that scoping is revisited.

🔧 Fix applied.
1 error still open:

  • 🚨 bin/fm-install-treehouse.sh:15 - The branch makes treehouse return --if-lease-holder a hard requirement, but the repo's own pinned CI build (FM_TREEHOUSE_CI_VERSION=2.0.1) does not have that flag. Verified against upstream source: cmd/return_cmd.go at tag v2.0.1 registers only --force; cmd/get.go at v2.0.1 does register --lease and --lease-holder, so only the release half is missing. Three concrete consequences on that build: (1) bin/fm-bootstrap.sh:950's widened treehouse_supports_lease gate now requires return --if-lease-holder to be advertised, so bootstrap prints MISSING: treehouse for the very binary bin/fm-install-treehouse.sh installs on the real-Herdr lane (.github/workflows/ci.yml:279); (2) bin/fm-spawn.sh:1266's aborted-spawn release runs treehouse return --force --if-lease-holder ..., which fails with an unknown-flag error, so the lease leaks and the printed recovery command is itself unusable — defeating the intent's requirement that every claim have a stated release path; (3) tests/fm-spawn-worktree-lease.test.sh:216 fails wherever that binary is on PATH, because the holder-matched return errors and trips fail &#34;real treehouse could not release the claim its own holder label took&#34;. Remedy (explicitly authorized in the recorded CI-round decision, which asked for this to be established and resolved): raise the pin to a release that advertises the flag — v2.1.0 is the version docs/verification/runtime-backends.md records and does carry --if-lease-holder — refresh the four platform SHA-256s, and update the 2.0.1-specific comments at lines 12, 17 and 72 plus the verification record so the pin and the recorded flag surface agree.

🔧 Fix applied.
1 info still open:

⚠️ **Test** - 1 info
  • ⚠️ tests/fm-spawn-dispatch-profile.test.sh:37 - tests/fm-spawn-dispatch-profile.test.sh installed a pass-through timeout stub that dropped only one argument (shift; exec &#34;$@&#34;). The branch's bounded pool allocation invokes timeout -k &lt;bound&gt; &lt;bound&gt; treehouse get --lease, so the stub tried to exec the bound itself (exec: 60: not found) and every spawn in the suite aborted with "treehouse get --lease could not lease a worktree". This is the CI lane 'Behavior portable serial 8'. Fixed by teaching the stub the GNU -k shape; the suite now passes with 59 checks.
  • ℹ️ tests/fm-public-followup.test.sh:60 - tests/fm-public-followup.test.sh exits 1 from its shared fixture cleanup, not from an assertion: all 74 checks pass with zero not ok, and the last line is rm: cannot remove &#39;/tmp/fm-public-followup.&lt;id&gt;&#39;: Directory not empty - a race between the still-terminating remote-job worker recreating files under remote-jobs/ and fm_test_cleanup's rm -rf, whose status becomes the EXIT trap's exit code. The branch's only change to this suite is swapping the treehouse stub, so this is a pre-existing cleanup flake rather than a regression; it will nonetheless make the lane red intermittently. Left as-is for the author to decide.
  • Live validation: ✅ go - 12 of 12 scenarios driven live against the product
Scenario Result Live Evidence
A paused task's pool copy is never handed to the next spawn: spawn task alpha, let its agent exit with committed work, spawn task beta - beta gets a different copy and alpha's branch and work are inta… ✅ pass live real-pool-spawn-does-not-reassign-live-copy.txt (real bin/fm-spawn.sh against a real treehouse v2.1.0 pool; slot 1 shows status leased, lease_holder fm:alpha-1@..., processes [])
Adversarial/regression: the same sequence on the pre-fix base commit hands beta alpha's copy and resets it to a detached HEAD ✅ pass live pre-fix-reproduces-reassignment.txt (base commit via git archive; REPRODUCED, HEAD detached, WIP file gone)
Second line of defence: a copy a claim-less live task record still names is refused by name, stays quarantined, and the re-run immediately succeeds on another copy ✅ pass live refusal-names-holder-and-recovery-command-works.txt (refusal names oldtimer-9; re-run launches on slot 2)
Operator recovery: the exact command the refusal prints, pasted verbatim from an unrelated working directory, releases the quarantined claim ✅ pass live refusal-names-holder-and-recovery-command-works.txt (run from /; slot 1 returns to status available)
Aborted spawn: a pane that never enters the leased copy leaves no task record and no orphaned claim ✅ pass live aborted-spawn-returns-claim-real-pool.txt (real pool; no .meta published, slot back to available)
Ordinary teardown release: treehouse return --force on the task's worktree frees the claim and returns the slot to the pool ✅ pass live teardown-release-frees-claim.txt and step 5 of real-pool-spawn-does-not-reassign-live-copy.txt
Relaunch keeps the task's claim: fm-spawn.sh <id> --relaunch rewrites the record and the original worktree_lease survives exactly once ✅ pass live relaunch-carries-claim-forward.txt (real relaunch through fm-spawn.sh with the lifecycle-modelling endpoint stub)
A stalled allocation refuses within its bound, names the holder a half-created slot would carry, and releases the shared Treehouse project lockdir ✅ pass live bash tests/fm-spawn-worktree-lease.test.sh - test_stalled_allocation_refuses_and_frees_the_project_lock, driving the real fm-spawn.sh CLI with FM_TREEHOUSE_GET_TIMEOUT=1
A task record that holds its own claim does not block a spawn the pool legitimately allocated ✅ pass live bash tests/fm-spawn-worktree-lease.test.sh - test_a_record_holding_its_own_claim_does_not_block_a_spawn
The widened bootstrap capability gate matches the CI pin: v2.1.0 advertises get --lease, get --lease-holder and return --if-lease-holder; the old v2.0.1 pin does not ✅ pass live treehouse-capability-gate.txt (probe run against both real binaries), ci-installer-v2.1.0.txt, treehouse-pin-checksums.txt (all four release checksums match)
The suites that failed the previous CI round are silent again: startup-memory-budget, session-start, secondmate-sync, agy-harness, spawn-dispatch-profile ✅ pass live suite runs - 4/54/33/30 checks green with no MISSING: treehouse; dispatch-profile failed on its timeout stub, was fixed, and re-ran green at 59 checks (dispatch-profile-timeout-stub-fix.txt)
Other spawn-path suites this branch touched still pass: base-branch, pool-base-freshen, tangle-guard, trace-context-spawn, worktree-settle, captain-hold-lifecycle ✅ pass live sweep output - 24/23/6/12/4/53 checks green, exit 0 for each
  • bash tests/fm-spawn-worktree-lease.test.sh (7 checks, including the real-treehouse pool-guarantee and holder-matched release cases)
  • Custom live driver: real bin/fm-spawn.sh + real treehouse v2.1.0, spawn task alpha-1 -> agent exits with committed work -> spawn beta-2 -> assert different copy and alpha's branch/work intact
  • Same driver against the base commit via git archive e80a29a4 | tar -x, reproducing the reassignment and detached-HEAD reset
  • Live aborted-spawn driver: pane never enters the leased copy -> assert no record published and treehouse status --json shows the slot available
  • Live refusal driver: legacy record naming a claim-less pool copy -> refusal names the task, re-run succeeds on another copy, printed recovery command pasted verbatim from / releases the quarantined claim
  • Live relaunch driver: bin/fm-spawn.sh &lt;id&gt; --relaunch with the lifecycle-modelling tmux stub -> worktree_lease preserved exactly once
  • treehouse get --lease --lease-holder / treehouse return --force against a real pool to confirm ordinary teardown frees the claim
  • Capability-gate probe run against real treehouse v2.1.0 and a downloaded v2.0.1 build
  • bash bin/fm-install-treehouse.sh &lt;tmpdir&gt; plus sha256sum of all four v2.1.0 release archives against the pinned checksums
  • bash tests/fm-startup-memory-budget.test.sh, tests/fm-session-start.test.sh, tests/fm-secondmate-sync.test.sh, tests/fm-agy-harness.test.sh (the previously CI-failing suites; all green, no MISSING: treehouse)
  • bash tests/fm-spawn-dispatch-profile.test.sh (failed, fixture fixed, re-run green at 59 checks)
  • bash tests/fm-spawn-worktree-settle.test.sh, tests/fm-spawn-base-branch.test.sh, tests/fm-spawn-pool-base-freshen.test.sh, tests/fm-tangle-guard.test.sh, tests/fm-trace-context-spawn.test.sh, tests/fm-captain-hold-lifecycle.test.sh (all green)

🔧 Fix applied.
1 info still open:

  • ℹ️ tests/fm-secondmate-liveness.test.sh:173 - tests/fm-secondmate-liveness.test.sh fails one case on this host: 'Herdr pane state unknown should map to unreadable, got missing'. fm_backend_herdr_agent_state falls back to fm_backend_herdr_server_running_state, which reports 'stopped' because a real herdr is installed here with no running session. Both functions are byte-identical at the base commit e80a29a, so this is a pre-existing environment-dependent failure, not a regression from the treehouse stub swap this branch makes to that suite.
  • Live validation: ✅ go - 12 of 12 scenarios driven live against the product
Scenario Result Live Evidence
A paused task's pool copy is never handed to the next spawn (real treehouse pool) ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/real-pool-spawn-does-not-reassign-live-copy.txt - beta-2 got slot 2 while alpha-1's leased slot 1 kept branch alpha-feature and its uncommitt…
Without the fix the same run reassigns the live copy and resets it (regression reproduces at base commit) ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/pre-fix-reproduces-reassignment.txt - REPRODUCED: beta-2 handed alpha-1's copy, HEAD detached, WIP gone
An aborted spawn gives its claim straight back, holder-matched, with no task record ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/aborted-spawn-returns-claim-real-pool.txt - pool status back to available, no .meta published
A copy a live pre-claim task's record names is refused by name, stays quarantined, and the spawn can be re-run ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/refusal-names-holder-and-recovery-command-works.txt and spawn-claim-transcript.txt - refusal names oldtimer-9/paused-task-w9 and the re-run g…
The printed recovery command releases the claim from any working directory and refuses a holder that does not hold it ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/recovery-command-live.txt - run from $HOME: wrong holder fails with 'lease precondition failed', correct holder returns the slot to the pool
Ordinary teardown frees a durable claim ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/real-pool-spawn-does-not-reassign-live-copy.txt step 5 and recovery-command-live.txt - treehouse return --force &lt;slot&gt; flips status leased…
A relaunch carries the task's original claim forward exactly once ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/relaunch-carries-claim-forward.txt
A stalled pool allocation refuses within its bound and leaves no project lockdir behind ✅ pass live bash tests/fm-spawn-worktree-lease.test.sh - 'a stalled allocation refuses within its bound and gives the project lock back' (evidence/lease-suite-run.txt)
The pinned CI treehouse installs, verifies its checksum, and advertises the three gated flags ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/ci-installer-v2.1.0.txt - real v2.1.0 download, installer exit 0, --lease/--lease-holder/--if-lease-holder advertised
Bootstrap's capability gate stays silent for the pinned build and still refuses one without holder-matched release ✅ pass live ~/.no-mistakes/evidence/01M2Z3AWVARJVY3AA814HYVJ1M/bootstrap-lease-gate.txt - empty output for v2.1.0, 'MISSING: treehouse' for a v2.0.1-shaped build
Spawns in the dispatch-profile suite survive the bounded timeout -k &lt;bound&gt; &lt;bound&gt; invocation (target commit) ✅ pass live bash tests/fm-spawn-dispatch-profile.test.sh - 59 checks pass, no 'could not lease a worktree' aborts (evidence/lease-suite-run.txt)
Suites that assert bootstrap silence are quiet again with the flag-modelling treehouse stub ✅ pass live bash tests/fm-startup-memory-budget.test.sh, tests/fm-session-start.test.sh, tests/fm-secondmate-sync.test.sh, tests/fm-agy-harness.test.sh - all exit 0, no 'MISSING: treehouse'
  • bash tests/fm-spawn-worktree-lease.test.sh (7 checks, includes real treehouse v2.1.0 pool cases)
  • bash tests/fm-spawn-dispatch-profile.test.sh (59 checks - the target commit's GNU -k timeout stub)
  • bash tests/fm-startup-memory-budget.test.sh, tests/fm-session-start.test.sh, tests/fm-secondmate-sync.test.sh, tests/fm-agy-harness.test.sh (the stale-stub suites)
  • bash tests/fm-bootstrap.test.sh, tests/fm-x-mode.test.sh, tests/fm-backend.test.sh, tests/fm-spawn-pool-base-freshen.test.sh, tests/fm-spawn-worktree-settle.test.sh
  • Manual live drive of bin/fm-spawn.sh against a real treehouse pool, post-fix (no reassignment of a paused task's copy) and pre-fix at base commit e80a29a (reassignment reproduced)
  • Manual live drive of the aborted-spawn release, the named second-line refusal, and the printed recovery command against a real pool from an unrelated cwd
  • bash bin/fm-install-treehouse.sh /tmp/fm-th-pin - real download, SHA-256 verification, then treehouse get --help / treehouse return --help flag advertisement
  • Live bin/fm-bootstrap.sh run with the pinned real binary on PATH, and adversarially with a v2.0.1-shaped build lacking return --if-lease-holder
  • Live treehouse get --lease --lease-holder / treehouse return --force / treehouse status --json checks of the teardown release path
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…der a live task

Supersedes #4, which cannot be revalidated: its branch was rewritten (the
pipeline rebased it onto a moved main, and custody was then returned at that
rebased head), and no-mistakes refuses to reconcile its private mirror record
afterwards. Same content, clean branch from main.

On 2026-08-19 a spawn was handed a pooled working copy that another task's own
record still named as its live copy. The pool reset it to a detached HEAD,
evicting a paused task from the copy holding its work; nothing was lost only
because that task had already pushed. Treehouse counts a slot as in use only
while a process runs inside it, so a task whose agent has exited - paused,
crashed, or between turns - leaves its copy looking free.

bin/fm-spawn.sh now takes the pool's own durable claim itself
(treehouse get --lease) instead of relying on the pane's subshell to hold the
slot, then moves the pane into the leased copy in a subshell so the pane's own
shell does not sit inside the worktree. Release paths: ordinary teardown returns
the worktree and the lease with it; an aborted spawn returns it holder-matched
in cleanup; otherwise the holder label names the owning record and the task
records worktree_lease= so a stale claim is self-identifying and releasable with
one command.

A named refusal stays as a second line of defence for the one copy a claim
cannot protect - a task spawned before claims existed, holding its slot with
nothing but its processes. The claim is deliberately kept on that refusal, so
the slot is quarantined rather than offered to the next spawn in a refuse-loop.

Upstream's slot-owner claim marker is kept as-is and this claim sits above it:
the lease stops the pool reassigning a live copy, the marker answers at teardown
whether the slot beside a record is still that record's own.
@talktejas
talktejas force-pushed the fm/spawn-pool-claim-r2 branch from 8f53ea7 to f1b760f Compare September 20, 2026 11:28
@talktejas
talktejas merged commit 0bbb028 into main Sep 20, 2026
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.

1 participant