fix(paths): respect treehouse's path spelling, canonicalize our own - #5
Merged
Merged
Conversation
Two path-aliasing bugs with one root cause and opposite fixes. Where /home is a symlink to /var/home (the default on every ostree/atomic Fedora variant), one directory has two spellings, and whether to canonicalize depends on who owns the spelling. teardown: `treehouse return` string-matches its argument against the spelling treehouse recorded, so every teardown failed on such a box with "is not managed by treehouse" - firstmate was hand-patching the meta with sed before each one. Firstmate never receives that spelling for a crew worktree: the worktree is found by polling the pane's cwd, which every backend reports OS-resolved. So ask treehouse's own inventory which spelling is its, matching on physical identity, at the handoff boundary - the single point where a path crosses back into treehouse, which also repairs metas written before this fix. Degrades to the caller's path when treehouse cannot be asked, so a changed output format is never worse than before. turn-end guard: fm_watcher_lock_matches_pid string-compared two of firstmate's OWN values, so a watcher armed from one spelling and a hook invoked through the other failed to match and the guard blocked turn ends at a live, beating watcher. Both sides are ours, so canonicalize both before comparing (fm_same_path). Same fix in fm-watch-arm.sh, which declined to clear its own stale lock for the same reason. The matcher stays strict: a genuinely different home still fails. Audited both classes. Orca already removes by opaque id and records its provider's path verbatim; herdr closes by id; fm_backend_hometag canonicalizes before hashing a home identity, which is correct. Recorded in docs/treehouse-path-contract.md, including the leased-row "(held by ...)" annotation a real run caught and a mock would not. Verified end to end against real treehouse v2.0.0, not only on mocks: pre-fix reproduces the exact error on a real lease, post-fix returns the worktree and `treehouse status` reports it available. Tests build the alias with ln -s rather than reading it off the host, so they hold on a box whose /home is not a symlink.
added 2 commits
July 16, 2026 16:45
Empty commit to fire the pull_request synchronize event now that GitHub Actions is enabled on the fork. No code changes.
DELIBERATE FORK DIVERGENCE FROM UPSTREAM POLICY - NOT A MISTAKE.
NEVER CONTRIBUTE THIS COMMIT UPSTREAM.
Upstream (kunchenguid/firstmate) requires every PR to be raised through
`git push no-mistakes`, and enforces it with this workflow. That policy is
correct for upstream and must stay there.
This fork does not run no-mistakes validation, so the gate fails on every
PR while checking nothing about the code: its only step greps the PR body
for the literal marker
Updates from [git push no-mistakes](...)
and fails when absent. It asserts nothing about correctness, tests, or
lint - the real signal is the CI workflow, which is untouched and stays
required.
Authorised by the captain for this fork only.
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.
Two path-aliasing bugs with one root cause and opposite fixes.
Where
/homeis a symlink to/var/home(the default on every ostree/atomic Fedora variant - Bazzite, Silverblue, Kinoite),$HOME/xand/var/home/<user>/xare one inode spelled two ways. Whether to canonicalize depends entirely on who owns the spelling:fm_same_path)treehouse_recorded_path)A blanket
pwd -Psweep acrossbin/fixes bug 2 and permanently entrenches bug 1.Bug 1: every teardown failed (external contract)
treehouse returnstring-matches its argument against the spelling treehouse recorded, resolving neither side, so teardown failed withis not managed by treehouseon every aliased box. Firstmate was working around it by hand:sed -i 's|^worktree=/var/home/|worktree=/home/|'before each teardown.Firstmate never receives treehouse's spelling for a crew worktree - the worktree is discovered by polling the pane's cwd, and every backend reports that OS-resolved (
pane_current_pathand friends read the kernel's physical path). There is no verbatim string to keep, sotreehouse_recorded_pathasks treehouse's own inventory which spelling is its, matching on physical identity rather than on the string.It resolves at the handoff boundary (
teardown_treehouse_return, the one function that callstreehouse return) rather than at spawn. That covers all three call sites, and repairs metas written before this fix without a migration. No/var/home->/homerewrite is encoded anywhere. If treehouse cannot be asked or reports no match, the caller's path passes through unchanged - never worse than before.Bug 2: turn-end guard cried wolf (internal comparison)
fm_watcher_lock_matches_pidstring-compared two of firstmate's own values. Every script derives its root with a logicalpwd, so the spelling follows the arming cwd: a watcher armed from one spelling recorded it in the lock, while Claude's Stop hook resolved the other - same directory, two strings, and the guard blocked turn ends at a watcher that was alive, holding the lock, and beating 1s earlier.Both sides are ours, so
fm_same_pathcanonicalizes both before comparing. The arming cwd is now irrelevant. Same fix infm-watch-arm.sh, which declined to clear its own stale lock for the same reason. The matcher stays strict - a genuinely different home still fails (covered by a test).Audit of both classes
--worktree "id:<id>"), records its provider's path verbatim, and its one path comparison canonicalizes both sides. No change.fm_backend_hometag- canonicalizesFM_ROOTbefore hashing into a label. Correct (stable identity for one home). No change.fm-home-seed.sh- canonicalizes thetreehouse get --leasepath, so a secondmate'shome=is physical rather than treehouse's. Harmless today because the teardown boundary re-resolves it; left alone deliberately (the registered string is compared elsewhere). Reported in the doc.bin/for raw path comparisons: the only other hits areGIT_DIR/GIT_COMMON_DIR(one git invocation, consistently spelled) and a dirname idiom. Both fine.Verification
A test on a non-symlinked home passes while both bugs survive - that is how this shipped. So:
ln -srather than reading it off the host, so coverage holds on any developer's box.exit 0, which accepts any spelling - exactly the blind spot.The real run earned its keep: it caught that a leased row trails a
(held by <holder>)annotation after the path, which my mock-only parser read as part of the path. It silently fell back and the return still failed. That shape is now a regression case (verified to fail without the fix), and both row shapes are recorded indocs/treehouse-path-contract.md.Notes
tests/fm-backend.test.sh's old-vs-new conformance diff now compares the mutating command sequence: the newtreehouse statusread is a deliberate read-only probe, outside that test's backend-refactor contract. The mutating sequence still must match exactly.tests/fm-session-start.test.shfails on the pristine base commit too (pi supervision block missing) - pre-existing and unrelated.