fix(bin): reconcile treehouse pool path spellings on a symlinked home - #2692
Open
Inthuson wants to merge 5 commits into
Open
fix(bin): reconcile treehouse pool path spellings on a symlinked home#2692Inthuson wants to merge 5 commits into
Inthuson wants to merge 5 commits into
Conversation
… home treehouse matches its managed inventory by string and builds pool paths from its configured root exactly as written, defaulting to a literal $HOME. fm-spawn.sh records the fully resolved physical worktree path instead, because the liveness and worktree-isolation checks must prove one exact directory. On a host whose home is a symlink the two records name a single directory yet differ as strings, so `treehouse return` refused the recorded path as "not managed by treehouse" and teardown aborted after it had already reaped the worktree processes. Every cleanup on such a host needed the recorded path rewritten by hand first, and the refusal text read like unlanded-work protection. Add bin/fm-treehouse-lib.sh as the single owner of the spellings that provably name one directory, and reconcile where the paths are used rather than making either side adopt the other's spelling, so neither the isolation checks nor treehouse's inventory change. A candidate is offered only when it resolves to the identical physical directory, so reconciliation can never act on a different worktree, and a total failure still reports the recorded path's own refusal rather than an alternative spelling's. fm-teardown.sh iterates the spellings itself so its index.lock patience window still applies to whichever spelling treehouse recognized. fm-home-seed.sh's seed rollback used the same raw return and warned that a lease had leaked when it had not. Four real-Herdr suites returned recorded paths that this host silently refused, orphaning a pool slot per spawn, and now release them.
Unblocking this suite's pool-path abort immediately exposed a real Herdr 0.8.2 defect that CI's exact 0.7.4 pin hides: closing a tab's last pane respawns a replacement pane, so a projected workspace never becomes empty, the emptying-close plan in bin/backends/herdr.sh never matches, and a projected workspace is never removed by a pane close. Every projected crewmate cleanup on 0.8.2 therefore orphans a workspace, and this suite's ordering assertions see the leftover fixture workspace. Skip on any release other than the exact pin rather than adapting the fixtures to whatever the local release does. Adapting them would report a green tick over a live defect, so that option was rejected; the 0.8.2 cleanup gap is filed separately as herdr-082-projection-orphan-o4. bin/fm-install-herdr.sh stays the single owner of the pin and is read rather than duplicated. An unreadable pin or an unreadable installed version reports the exact missing requirement and exits non-zero instead of skipping, because a skip nobody can trust would quietly disable this whole lane. The required Herdr CI lane also asserts the pin before it runs any suite, so a skip here cannot hide a mispinned runner. Verified: all four gate branches, meaning pin match, pin mismatch, unreadable installed version, and unreadable pin; the suite skips in 8ms on this 0.8.2 host while creating no fixtures and leaving the Herdr session list untouched; and bin/fm-test-run.sh records gate_skip=true with failed=0 without tripping the required lane's fail-on-gate-skip token.
…rification evidence
Owner
|
Speaking as Kun's firstmate: I reviewed the full diff (treehouse path-spelling reconciliation, teardown/seed callers, colocated tests) and approved fork CI. Waiting on green checks including no-mistakes. |
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
Stop firstmate having to hand-edit a recorded worktree path before every crewmate or scout cleanup on this host.
On this machine /home/inthuson is a symlink to /local/home/inthuson, so treehouse records pool worktree paths using its as-written $HOME-rooted spelling (/home/inthuson/...) while bin/fm-spawn.sh records the fully resolved physical path (/local/home/inthuson/...). Treehouse matches its managed inventory by string rather than by resolved path, so "treehouse return" refused with "is not managed by treehouse" and cleanup aborted at bin/fm-teardown.sh:2453 after it had already reaped the worktree processes, leaving the task half cleaned. Nine occurrences across two days, and every cleanup on this host needed a manual rewrite of worktree= in state/.meta first.
Required approach, as specified by the task: reconcile the two spellings rather than picking one, comparing paths after resolving both sides so a symlinked home stops being a special case. Explicitly ruled out: simply switching firstmate to the $HOME spelling without checking every other consumer of worktree=, because the physical path is what the liveness and worktree-isolation checks compare against, and breaking isolation detection to fix a cleanup annoyance would be a bad trade. The new bin/fm-treehouse-lib.sh is the single owner of the reconciliation and only ever offers spellings that provably resolve to the identical physical directory; retry and lock patience policy deliberately stays with the caller. bin/fm-home-seed.sh needed the same reconciliation because the identical defect silently leaked a durable lease during seed rollback.
Explicitly out of scope and deliberately not fixed here: teardown-shared-slot-guard-s2, the separate hazard that cleanup returns a pool slot without checking whether another task holds it.
Verification owed and completed: a real pooled worktree torn down on this host with no hand-editing; and a colocated regression in tests/fm-teardown.test.sh covering the symlinked-home spelling on both sides, since a happy-path-only test would be worthless here when the bug is a comparison that is wrong only when the two spellings differ.
The third owed item was making tests/fm-backend-herdr-presentation-e2e.test.sh complete rather than abort 77 lines before the code it exists to exercise, which was a silent coverage hole rather than a green tick. The abort is gone: the suite now reaches and passes five checks, including the previously unreachable opted-out spawn and teardown. It then failed on an unrelated assertion because this host runs herdr 0.8.2 while CI pins 0.7.4 exactly. On 0.8.2, closing a tab's last pane respawns a replacement pane, so a projected workspace never becomes empty, the exactly-one-tab-and-one-pane test in fm_backend_herdr_emptying_close_plan never matches, a projected workspace is therefore never removed by a pane close, and the leftover fixture workspace trails the ordering label list. That was confirmed by direct probe through the guarded Herdr lab helper, not inferred, and it is a real product defect whose captain-visible symptom is that every projected cleanup on 0.8.2 orphans a workspace in their own window.
Accepted decision on that item: bound the suite to the supported pin so a non-pinned host skips rather than fails, which keeps the signal honest without masking anything. Do not fix the 0.8.2 cleanup defect in this change; it is filed separately as herdr-082-projection-orphan-o4 at priority 1. Adapting or deleting the fixture to go green was considered and rejected outright, because it would hide the orphan defect behind a green tick. Downgrading herdr locally to the pin was also rejected, because it would disturb the live session holding the captain's own terminal and two parked validation runs. The pin gate reads the version from bin/fm-install-herdr.sh so that file remains the single owner of the pin, and an unreadable pin or unreadable installed version exits non-zero rather than skipping, because a skip nobody can trust would quietly disable this whole required lane.
The PR body must state four things: that unblocking this test immediately exposed a real 0.8.2 defect which CI's 0.7.4 pin hides, since that is the strongest argument for the change; that a correct path makes teardown-shared-slot-guard-s2's wrong return succeed rather than abort, so this change slightly raises the stakes on that separate task; that the reconciliation is bounded to the $HOME root alias plus the fully physical reverse direction and does not cover an arbitrary symlinked custom root in a repo's treehouse.toml; and that no docs/verification record is owed because nothing here is harness-dependent.
Constraints: this is firstmate's own shared tracked material, so firstmate-coding-guidelines applies; shellcheck-clean at the pinned 0.11.0; no em-dashes anywhere, including commit messages and the PR body; never add an agent name as a commit co-author.
Note for review: four failures in the changed-file test selection are pre-existing and unrelated to this change, proven by byte-identical failures on a pristine 1cb900c baseline clone: fm-backlog-handoff, fm-on, fm-remote-backlog-handoff, and fm-secondmate-lifecycle-e2e.
What Changed
bin/fm-treehouse-lib.shas the single owner of pool path spelling reconciliation between firstmate's recorded physical worktree paths and treehouse's string-matched inventory. It offers only spellings that provably resolve to the identical physical directory (recorded spelling first, then the$HOME-rooted rewrite, then the fully resolved path) and exposesfm_treehouse_return_force.bin/fm-teardown.sh'steardown_treehouse_returnnow tries each equivalent spelling before spending its gitindex.lockpatience window, headlining the recorded path's own refusal on total failure while still surfacing any other spelling's genuinely different failure;bin/fm-home-seed.sh's seed rollback uses the same reconciliation so a refused return no longer leaks a durable lease behind a warning. Retry and lock patience policy stays with each caller.tests/fm-backend-herdr-presentation-e2e.test.shno longer aborts before the code it exists to exercise: it now reaches and passes its five checks, including the previously unreachable opted-out spawn and teardown, and it gates on the exact Herdr pin read from the newbin/fm-install-herdr.sh --required-versionflag so a non-pinned host skips rather than fails. An unreadable pin or unreadable installed version exits non-zero instead of skipping. That suite plus the autodetect, launcher-workspace, and workspace-per-home real-Herdr suites return pool worktrees through the shared helper.tests/secondmate-helpers.shgainedFM_FAKE_TREEHOUSE_MANAGEDstring matching so fixtures mimic the real inventory refusal;tests/fm-teardown.test.shadds four colocated cases (reconciled return on a symlinked home, a genuinely unmanaged worktree still failing as the recorded path, a managed spelling's real failure surviving the recorded refusal, and a transient lock retried under the recognized spelling),tests/fm-secondmate-safety.test.shcovers the seed rollback path,bin/fm-test-run.shmaps the new library to thepr-forge,secondmate, andreal-herdr-gatedfamilies, andtests/fm-gotmp.test.shlinks it into its fake roots.Notes
fm_backend_herdr_emptying_close_plannever matches, and a projected workspace is never removed by a pane close. The captain-visible symptom is that every projected cleanup on 0.8.2 orphans a workspace in their own window. Confirmed by direct probe through the guarded Herdr lab helper, filed separately asherdr-082-projection-orphan-o4at priority 1, and deliberately not fixed here.teardown-shared-slot-guard-s2, which is out of scope here: with a correct path, cleanup's pool slot return now succeeds rather than aborting, so a return that never checks whether another task holds the slot will actually go through.$HOMEroot alias plus the fully physical reverse direction. It does not cover an arbitrary symlinked custom root configured in a repo'streehouse.toml.docs/verificationrecord is owed, because nothing here is harness dependent. Thedocs/verification/runtime-backends.mdedits only annotate that the presentation suite is now pin gated and that the dated runs on other releases predate that gate.bin/fm-install-herdr.shfor itsFM_HERDR_CI_VERSIONassignment. The goal of keeping one owner of the pin was right and the gate was accepted on that basis, but nobody caught that the mechanism breaks this repo's own rule that a test must never assert implementation source bytes, including through a regex, so a behavior preserving edit to that assignment could have silently disabled a required lane. The--required-versionflag added here is that correction rather than a tidy-up: it publishes the pin through an executable interface in the shapebin/fm-lint.sh --required-versionalready established, and the pin still has exactly one owner.Risk Assessment
✅ Low: The reconciliation is bounded by a provable same-physical-directory invariant, dedups to a single byte-identical attempt on non-symlinked hosts, every treehouse return call site in bin/ now routes through the one library, all five prior findings are verifiably fixed with genuine regression tests, and the only stakes-raising interaction (teardown-shared-slot-guard-s2) is explicitly acknowledged and separately tracked in the intent.
Testing
I reproduced the reported failure and its fix with the real treehouse binary and the real bin/fm-teardown.sh on a symlinked-home fixture: treehouse refuses the physically recorded pool path and keeps the lease, pre-fix teardown aborts at the return, and post-fix teardown completes with no hand-edited worktree= and the pool slot released. I then probed the claimed reconciliation boundaries against the real binary (reverse physical-root direction, recorded-first ordering, no $HOME candidate for a directory outside $HOME, single attempt for a path that no longer resolves). On the automated side I ran the changed-file portable suites that this host can run (fm-teardown, fm-secondmate-safety, fm-gotmp, fm-test-run, plus a spot check of fm-backlog-handoff which gate-skips here), proved both colocated regressions fail on a 1cb900c baseline tree carrying the new tests, added one focused teardown case for the reconciliation-plus-lock-patience interaction and mutation-checked it, and exercised the runner's changed-file mapping through bin/fm-test-run.sh --list --changed, which selects the consumer families for the new library where the pre-fix runner dies as unmapped. The Herdr presentation suite skips cleanly on this host's herdr 0.8.2 against the 0.7.4 pin and exits non-zero rather than skipping when either the pin owner or the installed version cannot be read, and it does not skip when the reported version equals the pin. This change is shell and CLI only with no rendered surface, so the evidence is CLI transcripts and treehouse's own pool status rather than screenshots. All tests and checks I ran passed; scratch trees are removed, the captain's herdr session and the $HOME treehouse pool are untouched, and the only working-tree change is the added test case.
Evidence: Real treehouse + real fm-teardown.sh on a symlinked home (the fix)
== the bug: real treehouse return --force with the recorded physical path == worktree /tmp/fm-th-e2e-lQmVLi/physhome/.treehouse/proj-c2b672/1/proj is not managed by treehouse exit=1 pool status: [{"path":".../home/.treehouse/proj-c2b672/1/proj","status":"leased","lease_holder":"fm-evidence"}] == recorded task meta (worktree= is the physical spelling, unedited) == worktree=/tmp/fm-th-e2e-lQmVLi/physhome/.treehouse/proj-c2b672/1/proj == the fix: real bin/fm-teardown.sh on that recorded path == 🌳 Worktree returned to pool. teardown: worktree return matched treehouse's pool path /tmp/fm-th-e2e-lQmVLi/home/.treehouse/proj-c2b672/1/proj for recorded worktree /tmp/fm-th-e2e-lQmVLi/physhome/.treehouse/proj-c2b672/1/proj (same directory, different spelling) teardown task-e2e complete (window firstmate:fm-task-e2e, worktree /tmp/fm-th-e2e-lQmVLi/physhome/.treehouse/proj-c2b672/1/proj) fm-teardown.sh exit=0 == treehouse's own pool status after teardown == [{"path":".../home/.treehouse/proj-c2b672/1/proj","status":"available","lease_id":"","lease_holder":""}]Evidence: Same real e2e against the pre-fix 1cb900c tree (the reported symptom)
== the fix: real bin/fm-teardown.sh on that recorded path == worktree /tmp/fm-th-e2e-bSX1si/physhome/.treehouse/proj-eecd16/1/proj is not managed by treehouse error: treehouse return failed for worktree /tmp/fm-th-e2e-bSX1si/physhome/.treehouse/proj-eecd16/1/proj; teardown aborted fm-teardown.sh exit=1 == treehouse's own pool status after teardown == [{"path":".../home/.treehouse/proj-eecd16/1/proj","status":"leased","lease_holder":"fm-evidence"}]Evidence: Reconciliation boundaries against the real treehouse (reverse direction, safety invariant, non-resolving record)
A. reverse direction: pool root physical, record $HOME-rooted raw treehouse return --force -> "is not managed by treehouse", exit=1, lease still held fm_treehouse_return_force -> 🌳 Worktree returned to pool. exit=0, pool status "available" B. recorded dir outside $HOME: only its own spelling offered; every spelling resolves to the recorded directory: yes B2. under a symlinked $HOME: physhome/wt then home/wt (recorded first) C. record that no longer resolves: attempts made: 1, refusal unchangedEvidence: Colocated regressions fail before the fix, pass after
against 1cb900c code: not ok - symlinked-home-pool-path: teardown should return the worktree by treehouse's own spelling of the same directory: expected exit 0, got 1 not ok - seed rollback reported a leaked lease instead of reconciling the two spellings against this change: ok - a physically recorded worktree is returned by treehouse's own $HOME-rooted spelling of it ok - a genuinely unmanaged worktree still fails, reported as the recorded path ok - an alternative spelling's genuinely different failure survives the recorded path's refusal ok - a transient lock under treehouse's own spelling is retried under that spelling, not the refused one ok - home seed rollback returns a physically recorded home by treehouse's own $HOME-rooted spellingEvidence: Herdr pin gate: honest skip here, loud failure when the pin or version is unreadable, no skip at the pin
pin published by bin/fm-install-herdr.sh --required-version : 0.7.4 installed herdr : herdr 0.8.2 real run on this host: skip: herdr 0.8.2 is not the suite-verified pin 0.7.4; install it with bin/fm-install-herdr.sh (exit=0) pin owner cannot answer: not ok - could not read the exact Herdr pin from bin/fm-install-herdr.sh --required-version (exit=1) installed version unreadable: not ok - could not read the installed Herdr version from ... (exit=1) version equals the pin: no skip line; the suite proceeds past the gate into lab provisioningEvidence: Mutation check for the case I added (reconciliation x lock patience)
control (unmutated c6fb6e5): ok - a transient lock under treehouse's own spelling is retried under that spelling, not the refused one mutated (dir=$spelling removed): not ok - managed-spelling-transient-lock: teardown should succeed once the lock under treehouse's own spelling clears: expected exit 0, got 1Evidence: Changed-file selection for bin/fm-treehouse-lib.sh alone (fixed runner selects pr-forge, secondmate, real-herdr-gated; pre-fix runner dies unmapped)
pre-fix runner: fm-test-run: no changed-test mapping for source path: bin/fm-treehouse-lib.sh (exit=2) fixed runner selects 35 scripts, including tests/fm-teardown.test.sh, tests/fm-secondmate-safety.test.sh, tests/fm-backend-herdr-presentation-e2e.test.shEvidence: Suite log: tests/fm-teardown.test.sh (reconciliation cases z, aa, ab, ac)
Evidence: Suite log: tests/fm-secondmate-safety.test.sh (seed-rollback reconciliation case)
Evidence: Reproducible demo scripts used for the manual verification
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-teardown.sh:169- fm-teardown.sh now sources a new sibling, $SCRIPT_DIR/fm-treehouse-lib.sh, but tests/fm-gotmp.test.sh runs the real teardown from a hand-built fake bin/ that symlinks each lib teardown sources one by one (tests/fm-gotmp.test.sh:63 and :143) and has no entry for the new file. SCRIPT_DIR resolves to that fake bin/ (the fixture exists precisely because BASH_SOURCE[0] is not dereferenced), so the source fails and, under teardown'sset -eu, the script exits 1 before it ever parses its argument. Failure:bash "$fake/bin/fm-teardown.sh" td-rm-z2prints "fm-treehouse-lib.sh: No such file or directory" and exits 1, so both test_teardown_removes_tasktmp_dir and test_teardown_skips_gracefully_without_tasktmp fail with "teardown exited non-zero with a valid tasktmp". bin/fm-teardown.sh maps only to the pr-forge family, so the changed-file selection never selects fm-gotmp.test.sh (pure-contract-unit), but CI's portable lanes run it. Fix: addln -s "$ROOT/bin/fm-treehouse-lib.sh" "$fake/bin/fm-treehouse-lib.sh"at both fixture sites, matching the fm-lock-lib.sh line above each.bin/fm-treehouse-lib.sh:1- The new lib has no entry in bin/fm-test-run.sh's families_for_changed_path, so it falls through thebin/*case to families_for_test_reference on its basename. Only the four real-Herdr suites source it by name, and all four are family real-herdr-gated, so a future change to bin/fm-treehouse-lib.sh selects only suites that gate-skip on any host without pinned herdr, and never selects pr-forge (tests/fm-teardown.test.sh, which owns this change's reconciliation regression) or secondmate (tests/fm-secondmate-safety.test.sh, which owns the home-seed rollback return). Failure: editing the spelling-candidate order in fm_treehouse_path_spellings and running the changed-file selection runs zero assertions about it on a non-pinned host, reporting green. bin/fm-nm-run-lib.sh at bin/fm-test-run.sh:937 is the existing precedent for a shared lib mapping to each consumer's family; add bin/fm-treehouse-lib.sh emitting pr-forge, secondmate, and real-herdr-gated.bin/fm-teardown.sh:1120- On total failure only the first attempt's output is reported. That is right when later attempts are "not managed by treehouse" refusals, but it also discards a later attempt's genuinely different failure. Failure: on the symlinked home this change targets, the recorded physical path is refused as unmanaged (first_out), then the $HOME spelling that treehouse does manage fails for a real reason such as an unreadable worktree or a failed reset; teardown prints only "worktree /local/home/... is not managed by treehouse" plus "teardown aborted", so the operator is pointed at the spelling defect this change just fixed instead of the actual cause, and the real error text is lost entirely. bin/fm-treehouse-lib.sh:120 has the same shape, where the seed-rollback warning then claims only that the lease may still be held. Suggest keeping first_out as the headline but still emitting any attempt output that is not an unmanaged-path refusal; flagging rather than changing it because the intent states this reporting rule deliberately.bin/fm-treehouse-lib.sh:96- fm_treehouse_return_force is a separate ~25-line loop from teardown's inline one, and no test covers it succeeding on an alternative spelling. The two new tests in tests/fm-teardown.test.sh drive teardown_treehouse_return's own loop, not this function. tests/fm-secondmate-safety.test.sh:327 only exercises the total-failure branch with an always-failing fake, and the four real-Herdr suites call it in best-effort cleanup behind|| trueand gate-skip without pinned herdr. Failure: invert the candidate order or drop theresolved = physicalguard in this function and every portable suite still passes, while the seed-rollback lease leak the intent names as the reason for touching fm-home-seed.sh silently returns. Mirror test_home_seed_returns_treehouse_acquired_home_on_assignment_failure with a symlinked HOME and a string-matching treehouse fake, the shape add_pool_spelling_treehouse already provides.tests/fm-backend-herdr-presentation-e2e.test.sh:38- The pin gate recovers the expected version by regexing bin/fm-install-herdr.sh's source withsed -n 's/^FM_HERDR_CI_VERSION=\([0-9][0-9.]*\)$/\1/p', and hard-fails the suite when that pattern does not match. That couples a required-lane suite to the byte shape of an implementation file, which the repo's own rule forbids (firstmate-coding-guidelines: "Tests must exercise behavior through an executable or public interface and must never assert implementation-source bytes, including through parsers, regexes, snapshots, or indirect wrappers"). Failure: a behavior-preserving edit to the install script, quoting the value as FM_HERDR_CI_VERSION="0.7.4" or appending a trailing comment, makes the sed return empty and this suite exits 1 with "could not read the exact Herdr pin", blocking the required Herdr lane on every PR while the install script itself still works. Keeping bin/fm-install-herdr.sh as the single pin owner is compatible with an interface: a--print-pinmode, or a tiny pin file both the installer and the suite read. Raising rather than changing it because the intent specifies reading the pin from that file.🔧 Fix: expose Herdr pin via flag and cover spelling reconciliation
2 infos still open:
bin/fm-teardown.sh:1127- A spelling-independent failure is now reported twice. When the first spelling fails for a reason that is neither an unmanaged-path refusal nor an index.lock signature, the loop continues to the next spelling, which then fails with the identical text; that text lands in first_out (printed at :1126) and again in other_out (printed at :1127). Concretely, the worktree return at bin/fm-teardown.sh:2502 passes cd_dir=$PROJ with no[ -d "$PROJ" ]orcommand -v treehouseguard, so on a symlinked home a missing project dir printscd: /.../project: No such file or directorytwice, and a missing treehouse binary printstreehouse: command not foundtwice, beforeteardown aborted. The same shape exists at bin/fm-treehouse-lib.sh:135. The intent behind keeping other_out was that a later spelling's genuinely different failure is the real cause; text identical to first_out is not a different failure. Suggest skipping an other_out entry equal to first_out.bin/fm-treehouse-lib.sh:127- The library copy of the later-attempt real-failure reporting added this round has no test. tests/fm-secondmate-safety.test.sh:327 covers only fm_treehouse_return_force succeeding on the alternative spelling, and the pre-existing test_home_seed_warns_when_acquired_home_return_fails drives an FM_FAKE_TREEHOUSE_RETURN_FAIL fake that exits 17 with no output, so other_out stays empty on both paths. Deleting lines 127-128 and 135 leaves every portable suite green while the seed-rollback warning again claims only that the lease may still be held, which is exactly the misleading message the round-1 fix existed to remove. The teardown twin is covered by test_managed_spelling_real_failure_is_not_hidden_by_the_recorded_refusal; the equivalent for the lib is one FM_FAKE_TREEHOUSE_MANAGED plus FM_FAKE_TREEHOUSE_MANAGED_FAIL-style case asserting the managed spelling's own error text reaches the seed's stderr. Separately, the third candidate at line 87 (the fully physical reverse direction) is live, since WT at bin/fm-teardown.sh:453 is the raw meta value and a meta hand-edited to the $HOME spelling reaches it, but no test exercises it either.tests/fm-backend-herdr-presentation-e2e.test.sh:22- The presentation suite's substantive claim (the abort is gone and the suite now reaches and passes five checks, including the previously unreachable opted-out spawn and teardown) is not verifiable on this host: herdr is 0.8.2 while the suite is bound to the 0.7.4 pin, and downgrading was explicitly ruled out to protect the live session. Locally I could only prove the skip path plus the gate's loud failure modes. Only the required Herdr CI lane, which asserts the exact 0.7.4 pin before running any suite, can confirm the five checks actually pass. You may want to decide whether CI confirmation is enough or whether you want that lane's output attached before merge.tests/fm-backend-herdr-workspace-per-home-e2e.test.sh:74- Three real-herdr-gated suites this change touches (fm-backend-autodetect-smoke, fm-backend-herdr-launcher-workspace-e2e, fm-backend-herdr-workspace-per-home-e2e) received the new fm_treehouse_return_force cleanup call but have no pin gate, so on this host they would run against the captain's live herdr 0.8.2 server and the real $HOME treehouse pool. I deliberately did not run them: on 0.8.2 a projected cleanup orphans a workspace (the defect this change itself documents as herdr-082-projection-orphan-o4), and they would acquire and reset real pool worktrees while parked validation runs hold slots. Their changed line is the shared cleanup call, which I verified directly against the real treehouse binary, so the residual gap is their own suite bodies at the pin, which the Herdr CI lane owns.bash tests/fm-teardown.test.shbash tests/fm-secondmate-safety.test.shbash tests/fm-gotmp.test.shbash tests/fm-test-run.test.shbash tests/fm-backend-herdr-presentation-e2e.test.shbash tests/fm-backlog-handoff.test.shReal-tooling e2e:treehouse get --leaseunder a symlinked HOME,treehouse return --force <physical>(refused, lease held), then realbin/fm-teardown.sh task-e2e(returns; pool leased -> available)Same e2e against a pre-fix 1cb900c tree (teardown aborted, exit 1, lease still held)Boundary probe offm_treehouse_path_spellings/fm_treehouse_return_forceagainst the real treehouse binaryPre-fix baseline runs of both colocated regressions (both fail without the fix)Mutation check: removingdir=$spellingfromteardown_treehouse_returnfails the case I addedbin/fm-install-herdr.sh --required-versionplus the three pin-gate paths (unreadable pin, unreadable version, version equals pin)bin/fm-test-run.sh --list --changed --base 1cb900cand an isolated single-path selection forbin/fm-treehouse-lib.sh🔧 **Document** - 2 issues found → auto-fixed ✅
docs/herdr-backend.md:318- Unblocking the presentation suite exposed a real Herdr 0.8.2 defect (closing a tab's last pane respawns a pane, so projected cleanup never removes the projected workspace and orphans one per cleanup), and docs/herdr-backend.md's "Presentation spaces" and "Active limits" sections state no such limit. I did not add it: this change does not alter product behavior on 0.8.2, that doc scopes its claims to the verified 0.7.1-0.8.0 set so nothing there is currently contradicted, and the intent files the defect separately as herdr-082-projection-orphan-o4 at priority 1. Judgment call for you: if that task will not land soon, the captain-visible limit is worth one line in docs/herdr-backend.md's "Active limits" (owned by that follow-up task, not this change), since this host itself runs 0.8.2..github/workflows/ci.yml:236- Out-of-scope follow-up: .github/workflows/ci.yml's "Assert Herdr pin and protocol floor" step hand-copies the pin value 0.7.4 twice, while this change made bin/fm-install-herdr.sh the queryable single owner via --required-version. The copies agree today so nothing is stale, and a docs phase cannot change workflow behavior, but reading the pin from the interface would remove the hand-copied duplicate the new flag exists to serve.🔧 Fix: note Herdr 0.8.2 projected-cleanup workspace orphan limit
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.