Summary
git trees add <branch> <base> silently creates the wrong branch when <base> names a branch
that exists only on the remote. The worktree is checked out on <base> instead of <branch>,
<branch> is never created, and the command exits 0.
Worse, it leaves behind a local <base> branch that nothing checks out. That ref then goes stale
as origin/<base> advances, and because cmd_add passes $base through verbatim, the next
git trees add <other> <base> bases the new worktree on the stale commit — also silently.
Both symptoms bit us in one session: a worktree created on the integration branch instead of a
feature branch (any commit would have gone straight onto the integration branch), and later a
worktree based one commit behind, missing a merged PR.
Root cause
cmd_add, in the "branch doesn't exist yet" path:
: "${base:=origin/$(_default_branch)}"
git worktree add --no-track -b "$br" "$dir" "$base" || return 1
$base is passed unqualified. When it names a remote-only branch, git's DWIM for
git worktree add takes over: it treats the start-point as "create a local branch tracking that
remote branch" and overrides the explicit -b "$br". The --no-track guard doesn't prevent
this — it only affects upstream, not the branch name.
Reproduction
git init -q --bare origin.git
git init -q work && cd work
git -c user.email=t@t -c user.name=t commit -q --allow-empty -m init
git remote add origin ../origin.git
git push -q origin HEAD:refs/heads/main
git push -q origin HEAD:refs/heads/v1.0.0-alpha.1
git fetch -q origin
git branch -D v1.0.0-alpha.1 # remote-only from here on
# exactly what cmd_add runs:
git worktree add --no-track -b newbranch ../wt-new v1.0.0-alpha.1
Actual:
Preparing worktree (new branch 'v1.0.0-alpha.1') <- not 'newbranch'
HEAD is now at 6a6a443 init
$ echo $?
0
$ git worktree list
.../wt-new 6a6a443 [v1.0.0-alpha.1] <- wrong branch
$ git for-each-ref --format='%(refname:short)' refs/heads
main
v1.0.0-alpha.1 <- stray local branch; 'newbranch' absent
Expected: worktree on newbranch, created at origin/v1.0.0-alpha.1, with no stray local ref.
Note git rev-parse --verify v1.0.0-alpha.1 fails in this state, so the bare name is not
resolvable as a commit-ish — the only reason the command succeeds at all is the DWIM path.
Fix
Either works; both verified against the repro above.
A — resolve the base to a commit before creating the worktree (also gives a clear error when
the base is bogus, instead of a confusing worktree):
: "${base:=origin/$(_default_branch)}"
base_sha=$(git rev-parse --verify "${base}^{commit}" 2>/dev/null) || {
echo "git trees add: base '$base' is not a valid commit" >&2; return 1; }
git worktree add --no-track -b "$br" "$dir" "$base_sha" || return 1
B — resolve a bare branch name to origin/<name> when there's no local ref, then pass that.
Suggest A: it fixes the DWIM hijack and the stale-local-ref problem in one go, since nothing named
$base is ever created locally.
Worth adding regardless: assert after creation that the worktree is actually on $br, so a future
DWIM change can't reintroduce a silent wrong-branch.
Environment
- git-trees 0.4.9 usage line:
add <branch> [base] [--print-path] [--no-push]
- git 2.x, macOS 26.6
Summary
git trees add <branch> <base>silently creates the wrong branch when<base>names a branchthat exists only on the remote. The worktree is checked out on
<base>instead of<branch>,<branch>is never created, and the command exits 0.Worse, it leaves behind a local
<base>branch that nothing checks out. That ref then goes staleas
origin/<base>advances, and becausecmd_addpasses$basethrough verbatim, the nextgit trees add <other> <base>bases the new worktree on the stale commit — also silently.Both symptoms bit us in one session: a worktree created on the integration branch instead of a
feature branch (any commit would have gone straight onto the integration branch), and later a
worktree based one commit behind, missing a merged PR.
Root cause
cmd_add, in the "branch doesn't exist yet" path:$baseis passed unqualified. When it names a remote-only branch, git's DWIM forgit worktree addtakes over: it treats the start-point as "create a local branch tracking thatremote branch" and overrides the explicit
-b "$br". The--no-trackguard doesn't preventthis — it only affects upstream, not the branch name.
Reproduction
Actual:
Expected: worktree on
newbranch, created atorigin/v1.0.0-alpha.1, with no stray local ref.Note
git rev-parse --verify v1.0.0-alpha.1fails in this state, so the bare name is notresolvable as a commit-ish — the only reason the command succeeds at all is the DWIM path.
Fix
Either works; both verified against the repro above.
A — resolve the base to a commit before creating the worktree (also gives a clear error when
the base is bogus, instead of a confusing worktree):
B — resolve a bare branch name to
origin/<name>when there's no local ref, then pass that.Suggest A: it fixes the DWIM hijack and the stale-local-ref problem in one go, since nothing named
$baseis ever created locally.Worth adding regardless: assert after creation that the worktree is actually on
$br, so a futureDWIM change can't reintroduce a silent wrong-branch.
Environment
add <branch> [base] [--print-path] [--no-push]