fix(bin): durably lease ship and scout worktrees - #8
Closed
sparkus wants to merge 9 commits into
Closed
Conversation
Plain treehouse get left pool slots available, so a later spawn could reuse a parked task's worktree and hard-reset unlanded work (kunchenguid#1441). Ship and scout spawns now acquire with treehouse get --lease --lease-holder <task-id>, cd the pane into that path, and keep isolation assertions. Recovery reuses a recorded worktree without a second lease. Successful teardown still returns (and thus releases); refused teardown never reaches return, so the lease stays held. Pre-lease unleased worktrees remain usable without adopting a lease.
Owner
Author
|
Closed on the captain's order. This ticket was mis-scoped: briefed as a small change to lease worktrees at spawn, it grew to 34 files and ~2,000 lines touching every backend adapter, the install path, bootstrap and CI. Discarding rather than landing a fleet-wide rewrite of worktree acquisition on a bad estimate. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Make ship and scout worktrees DURABLY LEASED at spawn so the worktree pool can never hand a still-owned slot to a new spawn and destroy unlanded work (kunchenguid#1441; real data loss observed 2026-08-03 when six concurrent ships reused parked AMPos pool slots and destroyed commit 89b59a2f).
Requirements implemented:
Acceptance tests (executable): leased slot not handed out by subsequent treehouse get; successful teardown releases lease; refused teardown leaves lease held (refusal exercised); recovery does not double-lease; pre-existing unleased worktrees still spawn/teardown.
Scope stays inside worktree lease lifecycle at spawn, teardown, and recovery - do not redesign the pool, backend layer, or teardown unlanded-work checks. This is firstmate shared tracked material.
What Changed
Risk Assessment
✅ Low: Captain, the registration now remains held through atomic metadata publication or abort, closing the prior retirement race while preserving independent concurrent spawns.
Testing
No baseline test results were supplied. The focused automated suite and end-to-end CLI/state checks demonstrated durable ship and scout leases, pool exclusion, refusal protection, recovery reuse, correct abort ownership, successful release, and legacy compatibility. This is CLI-only behavior, so screenshots were not applicable; transcripts capture the actual operator surface. Broad regression, lint, PR, and CI phases were not run because the outer executor owns them.
Evidence: Targeted lease lifecycle test
Evidence: Ship, recovery, refusal, release, and legacy CLI transcript
Evidence: Scout lease and teardown CLI transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 3 issues found → auto-fixed (6) ✅
bin/fm-spawn.sh:1330- Recovery treats a recorded directory's existence as proof that the task still owns it. Teardown returns the worktree at fm-teardown.sh:1462 but retains metadata when later Herdr endpoint confirmation fails at lines 1527-1531; that now-authorized recovery path re-enters an unleased slot which may already have been reassigned, recreating the data-loss failure. Complete refusal-capable cleanup before return or persist a returned/lease state that recovery validates; marker-absent legacy metadata can retain the explicitly required compatibility behavior.bin/fm-spawn.sh:1330- The new recovery path is not serialized with teardown: spawn holds.spawn-<id>.lock, but teardown never acquires it. Teardown can therefore inspect the old metadata and return the same lease while recovery is entering that worktree, after which spawn publishes fresh metadata and launches in a pool slot available to another get. Make spawn recovery and teardown share the task lifecycle lock from metadata inspection through return and metadata retirement.bin/fm-spawn.sh:1338- Host-side acquisition now requirestreehouse getto print a path, but several existing spawn-test fakes still return success with empty stdout, including fm-tangle-guard, fm-gate-refuse, fm-busy-adapter-wiring, fm-grok-harness, fm-kimi-harness, and fm-backend. Those successful-spawn cases now exit here, and fm-tangle-guard additionally still asserts the removed pane-sidetreehouse getcommand. Update these fixtures and assertions to model lease acquisition and the subsequent panecd.🔧 Fix: Captain, harden durable worktree lease recovery
3 errors still open:
bin/fm-teardown.sh:1351- Forced secondmate cleanup bypasses the new child lifecycle protocol: it neither acquires$sub_state/.spawn-$child_id.locknor reads the child's lease state/holder before an unguarded return. A child recovery can therefore validate and enter its leased worktree while parent teardown returns that slot, recreating the recovery/teardown race. Hold each child task lock from metadata inspection through retirement and use the same guarded state transitions; lease-marked return failures must fail closed instead of falling through tosafe_rm_rf_child_worktree.bin/fm-spawn.sh:1711- The new lease markers are written late in a truncating, non-atomic metadata publication. If spawn exits after writingworktree=but before both markers, abort cleanup returns the fresh lease, while the next spawn sees an empty lease state and misclassifies the partial metadata as authorized pre-change legacy, re-entering an available slot. Publish metadata through a same-directory temporary file plus atomic rename, and reject one-sided lease markers.bin/fm-teardown.sh:739- Guarded abort and teardown now requiretreehouse return --if-lease-holder, but the repository still installs and accepts Treehouse v2.0.1, whose return command has no such option; bootstrap checks onlyget --lease. Consequently abort cleanup silently leaks fresh leases and successful teardown fails on the repository's supported CI pin. Raise the supported/pinned version to v2.1.0+ and capability-check every required lease flag consistently.🔧 Fix: Captain, harden nested worktree lease lifecycle
1 error still open:
bin/fm-teardown.sh:1179-lock_firstmate_home_childrenlocks only task IDs represented by the*.metasnapshot. A new child spawn can acquire.spawn-new.lockwithout metadata during or after this loop, publish before the later cleanup glob, and have its freshly held lease returned without teardown owning its lock; publishing after that glob instead lets parent retirement remove the owning home and durable record beneath it. Add a home-scoped retirement boundary checked atomically by every child spawn before leasing, establish it before enumeration, retain it through home cleanup, and require ownership of every processed child lock.🔧 Fix: Captain, close child-spawn retirement race
1 warning still open:
bin/fm-spawn.sh:458- The retirement boundary is acquired only afterfm-wake-lib.shhas created$STATEandfm-guard.shmay have written home-local state. A child spawn can resolve its home, pause, let parent teardown return/remove it, then resume and recreate or mutate that retired—possibly reassigned—slot before observing the boundary. Acquire the boundary before any home-local initialization, or defer those side effects until it is held.🔧 Fix: Captain, guard home before spawn initialization
1 warning still open:
bin/fm-spawn.sh:199- Every spawn now takes the same nonblocking exclusive home boundary and holds it through guard, parsing, and backend setup. Two different task IDs started concurrently in one home therefore cause the second to fail here as “retirement” even when no teardown exists. Please confirm whether simultaneous distinct-task spawns must remain supported; if so, distinguish retirement ownership from transient spawn handoff, or wait and revalidate the home before proceeding.🔧 Fix: Captain, atomically coordinate parallel spawns and retirement
1 error still open:
bin/fm-spawn.sh:479- The required lifecycle contract says “Retirement begin ... MUST succeed only when no active spawn is registered” and “Unregister ... only on success or abort paths,” but this hunk unregisters immediately after acquiring the task lock, before lease acquisition or metadata publication. Parent teardown can consequently begin retirement while spawn A is still active; only the later child-task-lock scan notices A, while the transient retirement marker incorrectly refuses concurrent spawn B. Keep the home registration through successful atomic metadata publication or abort so retirement rejects A inside the shared-record critical section.🔧 Fix: Captain, retain registration through metadata publication
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
Baseline identity and scope:git status --short --branch,git rev-parse HEAD, andgit diff 2ef842cb530967264ba0f4835249f247b2c2fa40..9050bede312488ca9e3b356c0d5739ac0ee64e30bash tests/fm-spawn-worktree-lease.test.shRealfm-spawn.sh/fm-teardown.shagainst a deterministic Treehouse pool: leased allocation, blocked subsequenttreehouse get, recovery without reacquisition, recovered-publication abort, refused teardown, successful return, and legacy unleased compatibilityfm-spawn.sh scout-evidence-1 … --scout, blocked subsequenttreehouse get, then guarded scout teardown and slot release✅ **Document** - passed
✅ No issues found.
🔧 **Lint** - 1 issue found → auto-fixed ✅
🔧 Fix: Captain, export lifecycle channels and silence ShellCheck findings
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.