Skip to content

fix(bin): isolate Treehouse worker pools per home to prevent slot collisions - #2

Merged
gk-io-dev merged 7 commits into
mainfrom
fm/firstmate-treehouse-pool-isolation-recovered-20260917
Sep 17, 2026
Merged

gk-io-dev merged 7 commits into
mainfrom
fm/firstmate-treehouse-pool-isolation-recovered-20260917

Conversation

@gk-io-dev

@gk-io-dev gk-io-dev commented Sep 17, 2026 •

Copy link
Copy Markdown
Owner

Intent

Restore reliable worker allocation after evidence showed the primary Firstmate home and its persistent secondmate clone can resolve the same project clone to one shared reusable worktree pool.

What Changed

  • bin/fm-spawn.sh resolves each home's own Treehouse root via a new fm_treehouse_home_root helper, passes it as treehouse get --root <root> for fresh ship/scout allocations, records the absolute root as treehouse_root= in the task's state/<id>.meta, and refuses (fresh spawn and relaunch) to launch into a slot whose owner claim names a different canonical home with an extant task record.
  • bin/fm-teardown.sh reconciles each task's recorded treehouse_root (or derives it from the slot's own path for records that predate the field) before every treehouse return, passing it as --root, and refuses the return outright when the worktree isn't under that reconciled root — including in the descendant/child-worktree cleanup and forced-teardown preflight paths. Secondmate home leases continue to return without --root, unchanged.
  • bin/fm-wake-lib.sh adds the shared helpers backing this (fm_treehouse_home_root, fm_treehouse_task_root, fm_treehouse_slot_owner_state) used by both spawn and teardown.
  • bin/fm-bootstrap.sh now probes for both --lease and --root support (treehouse_supports_required_flags, replacing treehouse_supports_lease) and emits an upgrade diagnostic if either is missing; bin/fm-install-treehouse.sh bumps the pinned Treehouse release to v2.3.0 with updated per-platform checksums.
  • Documentation (docs/architecture.md, docs/configuration.md, the two .agents/skills files) and existing tests are updated to describe per-home pool isolation and the new TREEHOUSE_ROOT variable; adds tests/fm-treehouse-pool-isolation.test.sh covering allocation, recovery, and return under the new root reconciliation.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

Risk Assessment

✅ Low: Per-home Treehouse root derivation, task-root reconciliation, foreign/same-home claim logic, relative-root resolution, and teardown-return root plumbing are all internally consistent, correctly gated (ship/scout only, never orca/secondmate), fail-closed on ambiguity, and covered by behavioral tests that exercise real spawn/teardown scripts against fake and (gated) real Treehouse binaries rather than asserting on source text; docs match the implemented contract.

Testing

Ran the new dedicated suite (fm-treehouse-pool-isolation.test.sh) plus every other test file the diff touched, all end-to-end against the real shell scripts in isolated tmp homes, with one case driving an actual Treehouse v2.3.0 binary for lease/return. Every scenario passed with no failures; worktree left clean, no stray artifacts.

  • Live validation: ✅ go - 9 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Two Firstmate homes holding clones of the same repo allocate into isolated per-home pools instead of one shared pool ✅ pass live fm-treehouse-pool-isolation.test.sh: 'fm-spawn: two homes with clones of one repo allocate under two distinct home-scoped Treehouse roots and record them' — ok
Fresh spawn refuses a slot another home currently owns, even for the same task id (foreign-home guard, adversarial) ✅ pass live fm-treehouse-pool-isolation.test.sh: 'a slot Treehouse enters outside this home's root refuses rather than claiming it' and 'fresh allocation and legacy relaunch refuse foreign live claims, even with…
Same-home reuse of a clean, reusable slot is not blocked merely because an old same-home task record remains (R3/foreign-home-guard-preservation decision) ✅ pass live fm-treehouse-pool-isolation.test.sh: 'fm-spawn: same canonical home reuses a slot while its previous task record remains' — ok
Teardown returns a slot through its recorded Treehouse root, and refuses — even with --force — when the record no longer matches the slot's actual pool (adversarial) ✅ pass live fm-treehouse-pool-isolation.test.sh: 'a task returns its slot through the Treehouse root its record carries', 'a record without treehouse_root= still returns through the pool root that allocated it',…
Relative TREEHOUSE_ROOT (e.g. .) resolves against the spawning repository instead of aborting spawn (R4 fix) ✅ pass live fm-treehouse-pool-isolation.test.sh: 'relative roots resolve from the spawning repository and record absolute CLI roots' — ok
End-to-end allocation, crash recovery, and return against a real Treehouse v2.3.0 binary respects explicit and legacy roots ✅ pass live fm-treehouse-pool-isolation.test.sh: 'real Treehouse allocation, recovery, and return preserve explicit and legacy roots' — ok, visible 🌳 lease/return output from the actual installed treehouse CLI
Bootstrap reports an actionable upgrade diagnostic when the installed Treehouse lacks --lease or --root support (R1/R2 capability probe) ✅ pass live bash tests/fm-bootstrap.test.sh completed exit=0, including its treehouse capability-probe case
Secondmate home's own durable lease (fm-home-seed.sh) is unaffected by per-home ship/scout pool isolation ✅ pass live bash tests/fm-secondmate-safety.test.sh completed exit=0 (20 cases including force-teardown and slot-collision cases on the new .treehouse/ fixture layout)
fm-spawn sends treehouse get --root &lt;root&gt; to the stable window id, and a slot-claim failure surfaces the correct refusal message ✅ pass live bash tests/fm-tangle-guard.test.sh and tests/fm-spawn-pool-base-freshen.test.sh both completed exit=0, including the case asserting the exact treehouse get --root send-keys text and the 'unreadable…
Evidence: fm-treehouse-pool-isolation.test.sh run
ok - fm_treehouse_home_root: deterministic per canonical home, distinct across homes, path-safe under the Treehouse base
ok - fm-spawn: two homes with clones of one repo allocate under two distinct home-scoped Treehouse roots and record them
ok - fm-teardown: a task returns its slot through the Treehouse root its record carries
ok - fm-teardown: a record without treehouse_root= still returns through the pool root that allocated it
ok - fm-teardown: a recorded root that does not contain the slot refuses and changes nothing, even with --force
ok - fm-spawn: a slot Treehouse enters outside this home's root refuses rather than claiming it
ok - fm-spawn: same canonical home reuses a slot while its previous task record remains
ok - fm-spawn: fresh allocation and legacy relaunch refuse foreign live claims, even with the same task id
ok - fm-spawn: relative roots resolve from the spawning repository and record absolute CLI roots
ok - real Treehouse allocation, recovery, and return preserve explicit and legacy roots
Evidence: remaining touched suites (bootstrap, secondmate-safety, spawn-pool-base-freshen, tangle-guard, teardown-endpoint-safety)
=== fm-bootstrap.test.sh ===
ok - bootstrap validates crew-dispatch.json and reports malformed or unverified configs
exit=0
=== fm-secondmate-safety.test.sh ===
(20 cases, all ok)
exit=0
=== fm-spawn-pool-base-freshen.test.sh ===
(18 cases, all ok)
exit=0
=== fm-tangle-guard.test.sh ===
(6 cases, all ok)
exit=0
=== fm-teardown-endpoint-safety.test.sh ===
(20 cases, all ok)
exit=0

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 9 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Two Firstmate homes holding clones of the same repo allocate into isolated per-home pools instead of one shared pool ✅ pass live fm-treehouse-pool-isolation.test.sh: 'fm-spawn: two homes with clones of one repo allocate under two distinct home-scoped Treehouse roots and record them' — ok
Fresh spawn refuses a slot another home currently owns, even for the same task id (foreign-home guard, adversarial) ✅ pass live fm-treehouse-pool-isolation.test.sh: 'a slot Treehouse enters outside this home's root refuses rather than claiming it' and 'fresh allocation and legacy relaunch refuse foreign live claims, even with…
Same-home reuse of a clean, reusable slot is not blocked merely because an old same-home task record remains (R3/foreign-home-guard-preservation decision) ✅ pass live fm-treehouse-pool-isolation.test.sh: 'fm-spawn: same canonical home reuses a slot while its previous task record remains' — ok
Teardown returns a slot through its recorded Treehouse root, and refuses — even with --force — when the record no longer matches the slot's actual pool (adversarial) ✅ pass live fm-treehouse-pool-isolation.test.sh: 'a task returns its slot through the Treehouse root its record carries', 'a record without treehouse_root= still returns through the pool root that allocated it',…
Relative TREEHOUSE_ROOT (e.g. .) resolves against the spawning repository instead of aborting spawn (R4 fix) ✅ pass live fm-treehouse-pool-isolation.test.sh: 'relative roots resolve from the spawning repository and record absolute CLI roots' — ok
End-to-end allocation, crash recovery, and return against a real Treehouse v2.3.0 binary respects explicit and legacy roots ✅ pass live fm-treehouse-pool-isolation.test.sh: 'real Treehouse allocation, recovery, and return preserve explicit and legacy roots' — ok, visible 🌳 lease/return output from the actual installed treehouse CLI
Bootstrap reports an actionable upgrade diagnostic when the installed Treehouse lacks --lease or --root support (R1/R2 capability probe) ✅ pass live bash tests/fm-bootstrap.test.sh completed exit=0, including its treehouse capability-probe case
Secondmate home's own durable lease (fm-home-seed.sh) is unaffected by per-home ship/scout pool isolation ✅ pass live bash tests/fm-secondmate-safety.test.sh completed exit=0 (20 cases including force-teardown and slot-collision cases on the new .treehouse/ fixture layout)
fm-spawn sends treehouse get --root &lt;root&gt; to the stable window id, and a slot-claim failure surfaces the correct refusal message ✅ pass live bash tests/fm-tangle-guard.test.sh and tests/fm-spawn-pool-base-freshen.test.sh both completed exit=0, including the case asserting the exact treehouse get --root send-keys text and the 'unreadable…
  • bash tests/fm-treehouse-pool-isolation.test.sh (10/10 passed, includes a real Treehouse v2.3.0 binary allocate/recover/return case)
  • bash tests/fm-bootstrap.test.sh (passed, exit=0)
  • bash tests/fm-secondmate-safety.test.sh (passed, exit=0)
  • bash tests/fm-spawn-pool-base-freshen.test.sh (passed, exit=0)
  • bash tests/fm-tangle-guard.test.sh (passed, exit=0)
  • bash tests/fm-teardown-endpoint-safety.test.sh (passed, exit=0)
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Treehouse names a pool after the repository it serves, so the primary home
and a persistent secondmate holding clones of one remote resolved to a
single shared pool and could be handed each other's live slots. Only
Claude's workspace-trust check caught the observed cross-owner allocation;
no runtime-independent guard existed.

Every ship and scout spawn now acquires its slot with
`treehouse get --root <base>/fm-home-<hash>`, a deterministic root per
canonical FM_HOME owned by fm_treehouse_home_root in bin/fm-wake-lib.sh,
and records it as treehouse_root= in the task meta. Teardown reconciles
that record against the slot's actual root through fm_treehouse_task_root
and returns the slot with the same --root; a record whose root does not
contain its worktree refuses before any mutation, for the task itself and
for a secondmate's child tasks alike. A record without the field predates
it and returns through the pool that allocated it, so existing slots drain
with no migration, move, or reinterpretation. Secondmate homes stay the
primary's own lease under Treehouse's default root.

Spawn also refuses a slot whose owner claim names a task that still has a
task record in its home, on every harness and backend, and replaces only a
claim whose task record is gone.

tests/fm-treehouse-pool-isolation.test.sh pins the contract: two homes with
clones of one repo allocate to distinct roots, a task returns through its
recorded root, a legacy record returns through its original root, a
mismatched root refuses without mutation at spawn and teardown, and a
live foreign claim refuses while a stale one is replaced.
@gk-io-dev gk-io-dev closed this Sep 17, 2026
@gk-io-dev gk-io-dev reopened this Sep 17, 2026
…(the "wait for 3 fixed test files" watch) — its target logs were already checked manually and the relevant background tasks were stopped after confirming results. Fix remains: tests/fm-secondmate-sync.test.sh, tests/fm-startup-memory-budget.test.sh, tests/fm-session-start.test.sh each had their fake treehouse's `--help` output updated to advertise `--root` alongside `--lease`, resolving the false "MISSING: treehouse" diagnostics that broke Behavior portable serial 1, 2, and 5. No further action needed
@gk-io-dev
gk-io-dev merged commit a511c39 into main Sep 17, 2026
14 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