Skip to content

bin/check-worktree.sh reports the primary checkout from inside every worktree #450

Closed

Description

@growlf

Summary

bin/check-worktree.sh reports "in the primary checkout" from inside any linked worktree, so every legitimate worktree commit on a non-main branch prints a spurious warning. The check never actually distinguishes the two.

Reproduction

git worktree add /tmp/wt-demo -b demo-branch main
cd /tmp/wt-demo && bin/check-worktree.sh; echo "exit=$?"

Observed:

WARNING: On branch 'demo-branch' in the primary checkout.
  The primary checkout should normally be on 'main'.
  If this is intentional, proceed. Otherwise, create a worktree.
exit=0

Expected: OK: In worktree '/tmp/wt-demo' on branch 'demo-branch'.

The primary-checkout path is correct — on main it still refuses as intended:

ERROR: Refusing to commit on main in the primary checkout.
exit=1

Root cause

bin/check-worktree.sh:19-20 derives the repo root from the script's own location, and then uses that as the primary-checkout path:

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
...
PRIMARY_PATH="$(cd "$REPO_ROOT" && pwd)"     # line 44 — this is the *current* worktree

git worktree list --porcelain always includes an entry for the worktree you are standing in, so the loop at lines 49-59 compares each entry against the current worktree and matches it immediately:

for line in $WORKTREE_LIST; do
    case "$line" in
        worktree*)
            wt_path="${line#worktree }"
            if [ "$(cd "$wt_path" 2>/dev/null && pwd)" = "$PRIMARY_PATH" ]; then
                IS_PRIMARY=1     # always true, in any worktree

IS_PRIMARY=1 is therefore unconditional, and lines 84-91 (the "non-main branch in primary checkout" warning — a real condition worth flagging) fire for every worktree commit.

Secondary defect in the same loop: for line in $WORKTREE_LIST word-splits on whitespace, so a checkout path containing a space breaks the comparison, and HEAD <sha> / branch refs/heads/... lines are iterated pointlessly.

Impact

  • The warning fires on every worktree commit on a feature branch, which is the normal development path this script exists to encourage.
  • Alarm fatigue: the one condition worth catching — the primary checkout having been left on a feature branch — is indistinguishable from routine noise, and gets ignored.
  • A worktree deliberately on branch main prints the primary checkout warning (lines 84-91) instead of the intended "worktree on main" warning (lines 99-102).

Cosmetic in the sense that it still exits 0 and never blocks a commit; harmful in that it destroys the signal.

Proposed fix

Use git's own notion of the common repository rather than path guessing. In the primary checkout --git-dir and --git-common-dir are the same directory; in a linked worktree the former is <common>/worktrees/<name>.

git_dir="$(git rev-parse --absolute-git-dir)"
common_dir="$(git rev-parse --path-format=absolute --git-common-dir)"
if [ "$git_dir" = "$common_dir" ]; then IS_PRIMARY=1; else IS_PRIMARY=0; fi

Equivalently, the first worktree entry in git worktree list --porcelain is always the primary checkout — but the rev-parse comparison needs no parsing and is immune to spaces in paths.

Also replace the word-splitting loop with a while IFS= read -r line over the porcelain output.

Tests

There is currently no test file for this script, and nothing in Makefile or CI invokes it, so the defect has no guard. Worth adding tests/test_check_worktree.py covering:

Situation Expected
linked worktree, feature branch exit 0, no "primary checkout" text
primary checkout, main exit 1, refusal text
primary checkout, feature branch exit 0, warning (genuine)
linked worktree, branch main exit 0, worktree-on-main warning
worktree path containing a space resolves correctly

Environment

Found while committing from a linked worktree at ~/Projects/opskit-wt-kb-vault-cache (branch docs/kb-bw-cli-stale-cache) against main at f55f11e. Reproduced headlessly; no client environment involved.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpriority:lowLow priority

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions