Skip to content

fix(secondmate): point each cloned project at a per-home treehouse worktree pool - #4353

Open
jkartiwa wants to merge 7 commits into
kunchenguid:mainfrom
jkartiwa:fm/fm-treehouse-pool-per-clone-2
Open

jkartiwa wants to merge 7 commits into
kunchenguid:mainfrom
jkartiwa:fm/fm-treehouse-pool-per-clone-2

Conversation

@jkartiwa

@jkartiwa jkartiwa commented Sep 13, 2026 •

Copy link
Copy Markdown

Intent

Filed upstream as #4348; the evidence below mirrors that issue.

THE FAULT.

A secondmate cannot spawn a worker on any project its parent home has also cloned. bin/fm-spawn.sh refuses with "refusing to pre-register Claude trust: '' is not a worktree of project ''". The mate can be seeded, registered, launched and left idle looking healthy; the failure only appears at its first dispatch, with no local workaround available to it.

CAUSE, established by computation rather than inference.

treehouse names its worktree pool after the project's ORIGIN URL, not the clone asking. The pool directory is named from the project's basename plus the first six hex characters of sha256(origin URL). Two homes holding clones of one repository therefore share a pool, and every worktree in it is linked to whichever clone created it first.

Verified on the reporting machine with two genuinely distinct clones of one project (different no-mistakes gate repos), yet every worktree in the shared pool pointed at the parent clone's .git/worktrees/.

Worth knowing: no-mistakes already gets this right. Its gate repo name derives from the clone's own path, so each home has its own. Only the treehouse pool collides.

DO NOT RELAX THE GUARD. The refusal is correct. A secondmate worker running in a parent-owned worktree would validate through the PARENT's no-mistakes gate and record the run in the wrong home, draw from a pool the parent's teardown may return while that worker still holds it, and create fm/ branches in the parent's clone where two homes can collide on one name. Any fix that works by weakening or skipping the ownership assertion is the wrong fix and will be rejected.

THE APPROACH.

treehouse reads a treehouse.toml in the repository, and its root setting controls where the pool lives: worktrees are placed under {root}/.treehouse/, default $HOME. Writing that file into a project clone at the moment it is cloned into a secondmate home gives that home its own pool, attached to its own clone. No upstream treehouse change is needed. This was verified against a real treehouse acquire, not just inferred from documentation.

SCOPE, per the captain's later instruction to keep this PR as small as possible for a public repository with external maintainers: keep only (1) writing the treehouse settings file into a project clone at the point it is cloned into a secondmate home, pointing that home's pool at itself, covering every place a project is cloned into a secondmate home (there are two: a secondmate seeded on this host, and one provisioned on a remote host), (2) excluding the file locally so the clone does not read as dirty, and (3) one focused test proving two clones of one origin resolve to different pools. Drop the migration helper for already-seeded homes from this PR - mention it in the PR body as a note for anyone already running a secondmate instead. Drop any refactor, extra abstraction, config knob, or tidy-up. Do not add surface to make this general; the narrowest change that makes a secondmate able to work is the whole job.

Nothing identifying the captain's private projects or machine (real project names, origin URLs, home directory paths, usernames, or identifiers derived from them) may appear anywhere in this change, including tests and documentation.

What Changed

  • Added bin/fm-treehouse-pool-lib.sh, which derives a stable pool root from a home's absolute path and writes a treehouse.toml pointing at it into a freshly cloned project, adding the file to .git/info/exclude so the clone does not read as dirty.
  • Applied that configuration wherever a project is cloned into a secondmate home: local seeding (bin/fm-home-seed.sh) and remote provisioning (bin/fm-remote-home-provision.sh). A project that already tracks its own treehouse.toml is left untouched and warns rather than aborting the seed.
  • Added tests/fm-treehouse-pool-lib.test.sh, which uses the real treehouse binary to prove two clones of one origin acquire worktrees from distinct pools without dirtying either home, and routed it to the real-herdr-gated CI lane; documented the behavior in docs/configuration.md and the provisioning skill.

Note: a migration helper for already-seeded secondmate homes was intentionally left out of this change. Homes seeded before it keep their shared pool until their project clones are reconfigured; re-seeding or applying the same pool-root configuration to those clones resolves it.

Risk Assessment

✅ Low: The change is narrowly scoped, covers both project-clone sites, modifies project-clone-local config only, and its core invariant (two homes of one origin resolve to distinct treehouse pools) is asserted end-to-end against the real treehouse binary.

Testing

Ran the focused real-treehouse pool test against both the local v2.1.1 and the CI-pinned v2.0.1 (all pass), plus the secondmate and remote-secondmate lifecycle e2e suites. Independently reproduced the reported fault with two plain clones of one origin (shared pool, second clone handed the first's worktree), then demonstrated the fix end-to-end for both clone sites: a locally seeded secondmate home and a remotely provisioned home each produced a clean clone carrying a per-home treehouse.toml, and real treehouse acquires resolved to distinct pools linked to the correct clone. Also verified the corrected project-owned treehouse.toml warning through an actual seed: it names the project and file, no longer claims unconditional refusal, leaves the tracked file intact, and does not abort the seed. This is a CLI/state change with no rendered UI surface, so no screenshot artifact applies.

Evidence: Focused pool test under CI-pinned treehouse v2.0.1

Source: Focused pool test under CI-pinned treehouse v2.0.1

ok - fm_treehouse_configure_pool_root accepts two fresh clones of one origin
ok - the generated treehouse.toml is excluded locally, so both clones stay clean
ok - a project-owned treehouse.toml is left untouched with a named warning, not a seed abort
ok - each clone acquires from its own pool, even after the first home returns its slot
ok - neither home's own repository is dirtied by a project's pool acquire
Evidence: Before-fix reproduction: shared pool (treehouse v2.0.1)

Source: Before-fix reproduction: shared pool (treehouse v2.0.1)

home A pool: .../user-home/.treehouse/widget-9bb458
home B pool: .../user-home/.treehouse/widget-9bb458
REPRODUCED: both homes share one pool
REPRODUCED: home B is handed a worktree linked to HOME A's clone
Evidence: End-to-end local seed with per-home pool (treehouse v2.0.1)

Source: End-to-end local seed with per-home pool (treehouse v2.0.1)

root = ".../state/firstmate/treehouse-pools/c7bcc..."
(end status) # clone clean
parent pool: .../user-home/.treehouse/widget-56a8cf
secondmate pool: .../state/firstmate/treehouse-pools/c7bcc.../.treehouse/widget-56a8cf
OK: secondmate worktree links to the secondmate clone
OK: parent worktree links to the parent clone
Evidence: End-to-end remote provisioning with per-home pool (treehouse v2.0.1)

Source: End-to-end remote provisioning with per-home pool (treehouse v2.0.1)

root = ".../state/firstmate/treehouse-pools/cc5d3..."
(end status) # remote clone clean
remote .git: gitdir: .../remote-home/projects/widget/.git/worktrees/widget
OK: remote worktree links to the remote clone
Evidence: Project-owned treehouse.toml warning via a real seed

Source: Project-owned treehouse.toml warning via a real seed

warning: project widget keeps its own treehouse pool configuration in treehouse.toml, so this home's per-home pool root was not applied; worker spawns from this home may be refused if that configuration resolves to a pool shared with another home. Give that configuration a root unique to this home to avoid the collision.
home=.../design-home
root = "/srv/project-owned"

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • ⚠️ bin/fm-treehouse-pool-lib.sh:46 - The new guard at lines 46-48 returns 0 (warn-only) when the freshly cloned project already tracks treehouse.toml, so fm-home-seed.sh and fm-remote-home-provision.sh both report a successful seed while leaving that project on whatever pool root its repository specifies. That is a fourth behavior beyond the intent's enumerated scope ('keep only (1) writing the treehouse settings file ... (2) excluding the file locally ... (3) one focused test'), and it means the original fault can still be reached for such a project: if the tracked config resolves to the home-default $HOME pool, a secondmate worker is still handed a worktree of the parent clone and refused by bin/fm-claude-trust.sh. If the tracked root differs from the home root the guard is harmless, but the warning text asserts spawns 'will be refused' unconditionally. Reconciling a project-owned config (overwrite/merge, or a supported reconciliation step) would extend the change beyond its stated narrow scope, so the policy decision needs the user rather than a mechanical repair.
  • ℹ️ bin/fm-treehouse-pool-lib.sh:5 - The header says treehouse names a pool 'from a hash of the origin URL alone', but treehouse also includes the clone's project basename (verified with the CI-pinned 2.0.1 and local 2.1.1: both widget clones produced widget-352479, while differently named clones of one origin produced a-…/b-…). The collision requires the same project basename as well as the same origin, so the 'alone' wording understates what makes pools distinct and could mislead a future maintainer reasoning about the invariant; the code itself is correct.

🔧 Fix: Correct project-owned treehouse.toml warning to avoid false refusal claim
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-treehouse-pool-lib.test.sh (local treehouse v2.1.1) - all five assertions pass
  • PATH=/tmp/th201:$PATH bash tests/fm-treehouse-pool-lib.test.sh (CI-pinned treehouse v2.0.1, installed ephemerally via bin/fm-install-treehouse.sh) - all five assertions pass
  • bash tests/fm-secondmate-lifecycle-e2e.test.sh - full local seed/spawn/handoff/recovery/teardown lifecycle passes with the new clone-time call
  • bash tests/fm-remote-secondmate-lifecycle-e2e.test.sh - full remote host lifecycle passes, including remote seeding that clones the project on the remote host
  • Manual end-to-end: seeded a secondmate home via bin/fm-home-seed.sh, confirmed generated treehouse.toml points at the secondmate home's pool root, clone git status is clean, and real treehouse get --lease in the parent clone and secondmate clone resolve to distinct pools with each worktree linked to its own clone (treehouse v2.0.1 and v2.1.1)
  • Manual end-to-end: ran bin/fm-remote-home-provision.sh directly with a valid file:// manifest and confirmed the remote clone's treehouse.toml, clean status, and a real treehouse get --lease worktree linked to the remote clone
  • Manual before-fix reproduction: two plain clones of one origin with no treehouse.toml share one pool and the second clone is handed a worktree linked to the first clone's .git/worktrees/ (treehouse v2.0.1 and v2.1.1), confirming the regression the fix removes
  • Manual end-to-end: seeded a home whose origin tracks its own treehouse.toml; the seed emitted the corrected warning naming widget and treehouse.toml, stated the per-home root was not applied and that spawns may be refused only if the config resolves to a shared pool, left the tracked file untouched, and completed successfully
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…pool

Two firstmate homes cloning the same project origin shared one treehouse
worktree pool, because treehouse keys a pool on a hash of the origin URL
alone. Every worktree in that pool stayed linked to whichever clone created
it first, so a secondmate spawning work in a project its parent home also
clones was handed a worktree of the PARENT's clone - and fm-spawn.sh's
pre-registration guard correctly refused it, leaving the secondmate unable
to dispatch that project at all.

Point each freshly cloned project at its own pool instead, by writing a
treehouse.toml with a `root` key into the clone at the moment it is cloned
into a home, and excluding that generated file locally so the clone stays
clean. Two clone paths exist - a secondmate seeded on this host, and one
provisioned on a remote host - so both are covered.

Fixes kunchenguid#4348.
@jkartiwa

Copy link
Copy Markdown
Author

Requesting a workflow approval: this change is complete and green on our side (review, lint, tests, and the full pipeline through push), but the two required workflows on this PR are parked at action_required. GitHub will not run them on a fork PR until a maintainer approves them, and the fork owner has no write access to approve.

Could a maintainer approve the pending runs so CI can run and the change can be judged on its merits? If you would rather keep this surface with #4222, say so and I will close this one instead.

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