Skip to content

fix(spawn,teardown): compare worktree paths by identity, not string equality - #1439

Open
austinm911 wants to merge 1 commit into
kunchenguid:mainfrom
austinm911:fm/fm-isolation-bug
Open

fix(spawn,teardown): compare worktree paths by identity, not string equality#1439
austinm911 wants to merge 1 commit into
kunchenguid:mainfrom
austinm911:fm/fm-isolation-bug

Conversation

@austinm911

Copy link
Copy Markdown

Problem

Spawn compared directory paths as strings. On a case-insensitive, case-preserving filesystem (macOS default), /x/Coding/y and /x/coding/y name the same directory but compare as different strings.

The worktree-settle loop in fm-spawn.sh waits for the pane's cwd to differ from the project path before accepting it as the isolated task worktree. When FM_ROOT and the pane's reported cwd disagree only in case, that check passes immediately and spawn records the project clone itself as worktree= in state/<id>.meta.

That defeats the isolation assertion: it fails open, accepting the primary project directory as an "isolated worktree".

fm-teardown.sh then made it destructive. It ran, in this order:

branch=$(git -C "$WT" rev-parse --abbrev-ref HEAD)   # = main, because WT is the clone
git -C "$WT" checkout --detach -q
git -C "$WT" branch -D "$branch"                     # deletes the clone's default branch
...
teardown_treehouse_return ... || { echo "error: ..."; exit 1; }   # fails, aborts AFTER the delete

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_ROOT resolving as /Users/<u>/coding/firstmate while the pane reported /Users/<u>/Coding/firstmate:

project=  /Users/<u>/coding/firstmate/projects/loadout
worktree= /Users/<u>/Coding/firstmate/projects/loadout
[ "$a" = "$b" ]   # false  <- what the code used
[ "$a" -ef "$b" ] # true   <- the actual truth

Teardown deleted main in the clone and aborted. Correcting worktree= by hand made the same teardown succeed, isolating the cause.

Fix

bin/fm-spawn.sh — added same_directory() using the POSIX -ef same-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_of now 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:

  1. Refuses up front, before any mutation, when the recorded worktree and project are the same directory, naming both paths and the suspect metadata file.
  2. Defers hook cleanup and branch deletion until after teardown_treehouse_return succeeds, and deletes the branch via $PROJ rather 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 directory
  • tests/fm-teardown.test.sh — teardown refuses same-directory metadata before changing the project clone; branch is preserved when the return step fails

Full 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).

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread bin/fm-teardown.sh
Comment on lines +1446 to +1448
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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

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