Skip to content

finding(hooks): guard-main-checkout.sh reads any git-dir path containing /worktrees/ as a linked worktree — a PRIMARY checkout under a dir named worktrees is unguarded, and by DEPTH #7259

Description

@yinlianghui

Found while porting the sibling repo's self-test matrix for this hook (#6451). Out of scope for that card — that PR pins today's behaviour and says the hole out loud; it deliberately does not change the hook (.claude/** is governed surface, and the dispatch fence forbids weakening or "fixing" the guard in a matrix-only PR). Filed rather than fixed.

This is the objectui copy of objectstack-ai/objectstack#11809. The two hooks' executable lines are identical (their whole diff is 9 lines inside one comment block), so the defect is the same one, present here.

The line

.claude/hooks/guard-main-checkout.sh:42-44

case "$gitdir" in
  */worktrees/*) exit 0 ;;
esac

That is a substring match on a path, not a test for a linked worktree. A primary checkout that merely lives under a directory named worktrees produces a git-dir matching the pattern, and the guard exits 0 — an unguarded edit into a shared primary checkout, which is the exact failure worktree-first exists to stop.

Measured, not argued — and it is depth-dependent

git rev-parse --git-dir prints a RELATIVE git-dir (.git) at a repo's toplevel and an ABSOLUTE one from any subdirectory. Combined with the hook's nearest-existing-ancestor walk, the same unguarded checkout gets opposite verdicts by depth. With a fixture repo at TMP/worktrees/oddrepo:

payload file_path ancestor handed to git git-dir verdict today
ODD/README.md repo toplevel .git (relative) block — correct, but only by accident
ODD/brand/new/f.ts resolves up to toplevel .git (relative) block — ditto
ODD/pkg/x.ts a subdirectory absolute, contains /worktrees/ allow — WRONG
ODD/pkg/brand/new/f.ts a subdirectory absolute, contains /worktrees/ allow — WRONG

All four are pinned as cases in the matrix landing on #6451, under a KNOWN HOLE banner that names this card and states they are a record of today's behaviour, not of intended behaviour. When this is fixed, the last two flip to block and that section of the matrix becomes a mechanical edit.

Note the fixture path is not exotic: git worktree add ../repo-task is the prescribed recipe, and an operator who instead keeps trees under a worktrees/ parent directory gets a silently unguarded primary checkout.

Shape of the fix (not asserted)

The real question git can answer is whether the git-dir is a linked worktree of some repo, e.g. comparing --git-dir against --git-common-dir (they differ exactly in a linked worktree), rather than pattern-matching the path. Whoever picks this up should decide; both repos should move together, and the matrix on #6451 gives the change a red/green.

Filed unassigned; no domain:* or type label set.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions