fix(spawn,teardown): compare worktree paths by identity, not string equality - #1439
fix(spawn,teardown): compare worktree paths by identity, not string equality#1439austinm911 wants to merge 1 commit into
Conversation
Spawn compared directory paths as strings. On a case-insensitive, case-preserving filesystem such as macOS, /x/Coding/y and /x/coding/y can name the same directory but compare as different strings. Spawn could therefore record the project clone as the task worktree. Teardown could then detach the clone and delete its default branch before the fallible treehouse return step. Use -ef identity checks for spawn isolation, including the ancestor walk. Teardown now refuses same-directory task metadata and defers hook cleanup and branch deletion until the worktree return succeeds. Regression tests cover the case-variant spawn path, the teardown refusal, and branch preservation on return failure.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d553bdefbc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| rm -f "$WT/.claude/settings.local.json" "$WT/.opencode/plugins/fm-turn-end.js" \ | ||
| "$WT/.opencode/plugins/fm-busy-state.js" \ | ||
| "$WT/.fm-grok-turnend" "$WT/.fm-kimi-turnend" |
There was a problem hiding this comment.
Clean hooks before releasing the pooled worktree
Captain, after teardown_treehouse_return succeeds, $WT has been released to the pool and is no longer owned by this task; a concurrent spawn can lease the same path before these commands run, causing teardown to remove the new worker's hooks. If rm instead fails, teardown exits with stale metadata after releasing the lease, and retrying the original --force teardown can target that newly leased worktree. Remove or safely stage the hooks while ownership is still held, restoring them if the return fails, rather than accessing $WT after release.
AGENTS.md reference: AGENTS.md:L30-L32
Useful? React with 👍 / 👎.
Problem
Spawn compared directory paths as strings. On a case-insensitive, case-preserving filesystem (macOS default),
/x/Coding/yand/x/coding/yname the same directory but compare as different strings.The worktree-settle loop in
fm-spawn.shwaits for the pane's cwd to differ from the project path before accepting it as the isolated task worktree. WhenFM_ROOTand the pane's reported cwd disagree only in case, that check passes immediately and spawn records the project clone itself asworktree=instate/<id>.meta.That defeats the isolation assertion: it fails open, accepting the primary project directory as an "isolated worktree".
fm-teardown.shthen made it destructive. It ran, in this order:The project clone is left on a detached HEAD with its default branch deleted, and teardown exits non-zero with no rollback.
Reproduction
Observed live on macOS, twice, with
FM_ROOTresolving as/Users/<u>/coding/firstmatewhile the pane reported/Users/<u>/Coding/firstmate:Teardown deleted
mainin the clone and aborted. Correctingworktree=by hand made the same teardown succeed, isolating the cause.Fix
bin/fm-spawn.sh— addedsame_directory()using the POSIX-efsame-file test, with a string fallback when a path does not exist yet. Applied to the worktree-settle comparison,validate_spawn_worktree, and the secondmate home/operational-dir guards.path_is_ancestor_ofnow walks parents with the same identity test instead of a string prefix match, so it is also correct across case variants and symlinks.bin/fm-teardown.sh— two changes:teardown_treehouse_returnsucceeds, and deletes the branch via$PROJrather than detaching$WT. An aborted teardown can no longer leave the repository worse than it found it.Tests
tests/fm-spawn-worktree-settle.test.sh— case-variant spellings of the project are treated as the same directorytests/fm-teardown.test.sh— teardown refuses same-directory metadata before changing the project clone; branch is preserved when the return step failsFull suites pass: spawn-worktree-settle, teardown, teardown-endpoint-safety, spawn-dispatch-profile, secondmate-safety (134 assertions). ShellCheck clean on both scripts (the one remaining SC2034 is pre-existing).