fix(bin): lease a secondmate's worktree from its own clone - #64
Merged
Merged
Conversation
Treehouse names a pool by directory name and origin URL, not by clone
path, so a secondmate home's clone of a project shares the pool of the
main home's clone of the same origin. A spawn from the secondmate home
was handed a copy linked to the main home's clone, and Claude trust
pre-registration refused it ("is not a worktree of project"), so no
claude worker could start there.
Acquisition now holds such a copy leased while it retries, so Treehouse
cannot hand it out again and creates a copy from the spawning clone,
then returns the held copies. The trust refusal is unchanged for fresh
spawns. A relaunch of a task already recorded in another clone's copy
of the same origin registers trust against that clone, so existing work
can move to claude; an unrelated repository's copy is still refused.
Refs kunchenguid#4348
The free-or-dead-lock case made its dead pid with `sleep 30 &` followed at once by `kill`. A TERM that reaches the forked child before it drops the suite's TERM trap runs fm_test_cleanup there, and the child shares the suite's cleanup registry, so it deleted the fixture root while the test kept running. CI saw the second extension load fail with ERR_MODULE_NOT_FOUND. A child that exits on its own yields a dead pid without sending any signal.
tiago-peixoto
added a commit
that referenced
this pull request
Sep 25, 2026
* fix(bin): lease a secondmate's worktree from its own clone
Treehouse names a pool by directory name and origin URL, not by clone
path, so a secondmate home's clone of a project shares the pool of the
main home's clone of the same origin. A spawn from the secondmate home
was handed a copy linked to the main home's clone, and Claude trust
pre-registration refused it ("is not a worktree of project"), so no
claude worker could start there.
Acquisition now holds such a copy leased while it retries, so Treehouse
cannot hand it out again and creates a copy from the spawning clone,
then returns the held copies. The trust refusal is unchanged for fresh
spawns. A relaunch of a task already recorded in another clone's copy
of the same origin registers trust against that clone, so existing work
can move to claude; an unrelated repository's copy is still refused.
Refs kunchenguid#4348
* test(pi): stop a killed fork from deleting the dead-lock fixture
The free-or-dead-lock case made its dead pid with `sleep 30 &` followed
at once by `kill`. A TERM that reaches the forked child before it drops
the suite's TERM trap runs fm_test_cleanup there, and the child shares
the suite's cleanup registry, so it deleted the fixture root while the
test kept running. CI saw the second extension load fail with
ERR_MODULE_NOT_FOUND. A child that exits on its own yields a dead pid
without sending any signal.
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.
Problem
A second mate cannot start or relaunch any worker on the claude harness.
Spawns fail with:
Upstream issue: kunchenguid#4348 (duplicate report: kunchenguid#4045).
Root cause
Treehouse names a pool
<basename>-<sha256(origin URL)[:6]>(internal/config/config.goResolvePoolDirin treehouse v2.3.0), not by clone path.The main home's
projects/artemisand the second mate'sprojects/artemishave the same basename and origin, so they share~/.treehouse/artemis-b0df83.Its reuse scan (
internal/pool/pool.goacquire) hands out any idle slot regardless of which clone it is linked to, and every existing slot there is linked to the main home's clone.The trust refusal is correct: that copy is not the spawning project's worktree, and the slot claim and teardown's project lock also skip it.
Fix
The fix is in lease acquisition in
bin/fm-spawn.sh, which every Treehouse spawn goes through.Trust registration still refuses these copies.
Treehouse then cannot hand it out again and creates a copy from the spawning clone.
The held copies are returned when acquisition finishes, whether it succeeds or fails.
fm-claude-trust.shstill checks that the worktree is linked to it.Without this, work already in flight in the Artemis home could not move to claude.
A copy of an unrelated repository is still refused.
I considered a per-home
treehouse --root(kunchenguid#4222) and rejected it here.It moves every home's pool, including the main home's pools that hold live leases, and it raises the treehouse version floor.
This fix needs neither.
Trade-off: the two clones still share
max_treescapacity, and a spawn may reset a few of the other clone's idle copies while it retries.Evidence
Reproduced first, then fixed (
tests/fm-spawn-worktree-lease.test.sh):test_spawn_with_real_treehouse_launches_in_its_own_cloneruns installed treehouse 2.3.0 end to end.The main clone leaves an idle copy in the shared pool, then a claude spawn runs from a second-mate-shaped home with its own clone.
Before the fix it failed with the exact reported refusal; after the fix it launches in a copy of its own clone and returns the main clone's copy.
It skips when treehouse is not installed.
test_spawn_skips_a_copy_of_another_clonecovers the same case with the fake pool, so it also runs in CI.test_relaunch_trusts_a_recorded_copy_of_another_clonefailed before the fix and passes after it.test_relaunch_still_refuses_a_copy_of_an_unrelated_repositorypins the refusal that stays.Also run:
bin/fm-lint.sh,bin/fm-doc-audience-check.sh,tests/fm-claude-trust.test.sh, and alltests/fm-spawn-*.test.sh, plustests/fm-secondmate-safety.test.sh.All passed.
tests/fm-spawn-compact-adviser-disable-remote.test.shfailed once while another suite ran concurrently, then passed 3 of 3 reruns.It does not touch Treehouse.