Repository navigation
bin/check-worktree.sh reports the primary checkout from inside every worktree #450
Description
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* worktreegit 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 worktreeIS_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
mainprints the primary checkout warning (lines 84-91) instead of the intended "worktree onmain" 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; fiEquivalently, 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.
Activity
fix(scripts): detect the primary checkout from git's own repo layout (#…