Skip to content

test(worktree): regression guard for .git file vs directory (#521)#523

Merged
bradygaster merged 3 commits intobradygaster:devfrom
diberry:squad/521-worktree-tests
Mar 23, 2026
Merged

test(worktree): regression guard for .git file vs directory (#521)#523
bradygaster merged 3 commits intobradygaster:devfrom
diberry:squad/521-worktree-tests

Conversation

@diberry
Copy link
Copy Markdown
Collaborator

@diberry diberry commented Mar 22, 2026

Summary

Closes #521

Working as EECOM (Core Dev)

Adds \ est/worktree.test.ts\ — 7 regression tests covering the root causes identified in Flight's worktree investigation.

What this does

4 tests that FAIL on current code (proving the bugs exist):


  • esolveSquad()\ treats a .git\ FILE the same as a directory — returns
    ull\ instead of falling back to the main checkout's .squad/\
  • \detectSquadDir()\ returns a non-existent \worktree/.squad\ path when called from a worktree

3 tests that PASS as positive controls:

  • .git\ directory still correctly marks the repo root boundary

  • esolveSquad()\ returns null when neither location has .squad/\
  • \detectSquadDir()\ works correctly in a normal (non-worktree) checkout

Files changed

  • \ est/worktree.test.ts\ — new regression test file
  • \packages/squad-cli/package.json\ — exposes ./core/detect-squad-dir\ export so tests can import it cleanly

CI

The test file matches the \ est/**/*.test.ts\ include pattern in \�itest.config.ts\ and runs with
pm test\ — no special setup required.

Once the SDK and CLI fixes from the investigation land, all 4 failing tests should flip green.


Investigation by: Flight (Keaton) · Requested by: Dina

diberry pushed a commit to diberry/squad that referenced this pull request Mar 22, 2026
…, add statSync guard

- Remove vi.mock('child_process') and all helpers — implementation reads
  .git via fs.readFileSync, never calls child_process; the mock was dead
  code that gave false confidence

- Fix gitdir in all worktree test fixtures:
  '../../.git/worktrees/feature-521' resolved above the temp dir;
  corrected to '../main/.git/worktrees/feature-521' which resolves to
  tmp/main/.git/worktrees/feature-521 as expected

- Create main/.git/worktrees/feature-521/ directory in each fixture so
  the real directory structure matches the gitdir pointer

- Add statSync guard to getMainWorktreePath() (resolution.ts) and
  resolveWorktreeMainCheckout() (detect-squad-dir.ts): verify that
  mainCheckout/.git exists and is a directory before trusting it; guards
  against crafted .git files that redirect Squad to attacker-controlled paths

- Add two statSync guard tests: resolveSquad() and detectSquadDir() both
  return null/fallback (not crash) when gitdir points to a non-existent path

All 9 worktree regression tests pass.

Fixes PR bradygaster#523 blockers as requested by Dina.
Working as FIDO (Quality Owner)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@diberry
Copy link
Copy Markdown
Collaborator Author

diberry commented Mar 22, 2026

squad obo dina — PR #523 is fully team-reviewed and approved.

Reviewer Verdict
Flight (Lead) ✅ Approved
CONTROL (TypeScript) ✅ Approved
RETRO (Security) ✅ Approved — statSync guard verified
FIDO (Quality) ✅ Approved — 9 worktree tests pass
Auto-scan ✅ Implementation correct

Fixes #521 (worktree bug reported by Yoni Ben-Ami). 3 commits, 3 fix cycles, 5 reviewers. Ready for your review @bradygaster.

Copilot AI added 3 commits March 22, 2026 15:19
…ygaster#521)

Adds test/worktree.test.ts — 7 tests covering the two root causes
identified in Flight's worktree investigation:

resolveSquad() (packages/squad-sdk/src/resolution.ts)
- Stops at a .git FILE as if it were a repo root boundary
- Should fall back to \git worktree list --porcelain\ to find the
  main checkout, then return its .squad/

detectSquadDir() (packages/squad-cli/src/cli/core/detect-squad-dir.ts)
- Returns worktree/.squad (non-existent) when called from a worktree
- Should use the same worktree fallback to find the main checkout

4 tests FAIL on current code (proving the bugs).
3 tests PASS as positive controls.
All tests will PASS once the fixes described in the investigation doc
are applied.

Also exposes detect-squad-dir via the CLI package exports so it can be
imported by tests without reaching into dist/ directly.

Requested by: Dina
Investigation by: Flight (Keaton)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
resolveSquad() and findSquadDir() in packages/squad-sdk/src/resolution.ts
now distinguish .git as a directory (real repo root) from .git as a file
(git worktree pointer). When .git is a file, the gitdir: pointer is parsed
to derive the main checkout path, and .squad/ is checked there as a fallback
(main-checkout strategy). Worktree-local .squad/ continues to take priority.

detectSquadDir() in packages/squad-cli/src/cli/core/detect-squad-dir.ts
gains the same worktree fallback via a new exported helper
resolveWorktreeMainCheckout(). All 8 CLI commands that call detectSquadDir
now transparently find the main checkout's .squad/ when running from a
worktree root.

squad init gains an explicit worktree guard: when running from a worktree
that already has .squad/ in the main checkout, the user is prompted to
choose between shared strategy (use main checkout, no files created) and
worktree-local strategy (scaffold .squad/ in the worktree). Non-interactive
sessions default to shared to prevent silent duplicate creation.

Three new tests in test/resolution.test.ts cover the main-checkout fallback,
worktree-local priority, and the full walk-up scenario.

Closes bradygaster#521

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…, add statSync guard

- Remove vi.mock('child_process') and all helpers — implementation reads
  .git via fs.readFileSync, never calls child_process; the mock was dead
  code that gave false confidence

- Fix gitdir in all worktree test fixtures:
  '../../.git/worktrees/feature-521' resolved above the temp dir;
  corrected to '../main/.git/worktrees/feature-521' which resolves to
  tmp/main/.git/worktrees/feature-521 as expected

- Create main/.git/worktrees/feature-521/ directory in each fixture so
  the real directory structure matches the gitdir pointer

- Add statSync guard to getMainWorktreePath() (resolution.ts) and
  resolveWorktreeMainCheckout() (detect-squad-dir.ts): verify that
  mainCheckout/.git exists and is a directory before trusting it; guards
  against crafted .git files that redirect Squad to attacker-controlled paths

- Add two statSync guard tests: resolveSquad() and detectSquadDir() both
  return null/fallback (not crash) when gitdir points to a non-existent path

All 9 worktree regression tests pass.

Fixes PR bradygaster#523 blockers as requested by Dina.
Working as FIDO (Quality Owner)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@diberry diberry force-pushed the squad/521-worktree-tests branch from ebc0efc to 961cbba Compare March 22, 2026 22:20
@bradygaster bradygaster merged commit 70f48cb into bradygaster:dev Mar 23, 2026
0 of 2 checks passed
chrislomonico pushed a commit to clomonico/squad that referenced this pull request Mar 26, 2026
…r#546)

Consolidated 15 remaining issues into a single PR. Redesigns help UX with grouped categories, surfaces all /help commands, aligns docs with actual CLI behavior, adds first-run welcome, standardizes naming.

Closes bradygaster#510, Closes bradygaster#513, Closes bradygaster#514, Closes bradygaster#515, Closes bradygaster#516, Closes bradygaster#517, Closes bradygaster#518, Closes bradygaster#521, Closes bradygaster#522, Closes bradygaster#523, Closes bradygaster#524, Closes bradygaster#525, Closes bradygaster#527, Closes bradygaster#528, Closes bradygaster#529

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

bug: Squad breaks in git worktrees — .git file vs directory not distinguished

3 participants