In a detached worktree, alias and hook templates render {{ branch }} as the literal HEAD. wt list --json reports branch: null for the same worktree, and the hook docs say an undefined variable errors so a template can guard it with a conditional. HEAD does neither: it is a string that git happily resolves as a ref, so every guard written around branch passes and the command runs against the wrong thing.
What I hit, with wt v0.76.0. An alias tidies a merged feature worktree and deletes its remote branch:
[aliases]
landed = 'git merge {{ default_branch }} --no-edit{% if branch != default_branch %} && git rev-parse --verify -q {{ remote }}/{{ branch }} >/dev/null && git push {{ remote }} --delete {{ branch }}{% endif %}'
Run from a worktree created with git worktree add --detach ../repo.scratch origin/main, branch renders as HEAD, so branch != default_branch passes; origin/HEAD is a symbolic ref in any ordinary clone, so the existence check passes too; and the alias ends with:
git push origin --delete HEAD
error: unable to delete 'HEAD': remote ref does not exist
Smallest repro:
[aliases]
probe = 'echo "[{{ branch }}]"'
git worktree add --detach ../repo.scratch origin/main
cd ../repo.scratch
wt -v probe
-v prints branch = HEAD in the template-variables block and the alias echoes [HEAD].
The fallback is deliberate: CommandContext::branch_or_head substitutes HEAD when there is no branch, and the hook pipeline matches it. Expected instead is one of:
- Leave
branch unset in a detached worktree, so {% if branch %} guards it the way the docs already prescribe for upstream, and it agrees with the JSON output. This is the one I would pick.
- Or expose a
detached variable alongside it.
The workaround today is {% if branch != "HEAD" %}, which holds only because git refuses HEAD as a branch name, and nothing documents that HEAD is what a detached worktree renders.
Seen on worktrunk bc2ec12.
This was written by Claude Code on behalf of max-sixty
In a detached worktree, alias and hook templates render
{{ branch }}as the literalHEAD.wt list --jsonreportsbranch: nullfor the same worktree, and the hook docs say an undefined variable errors so a template can guard it with a conditional.HEADdoes neither: it is a string that git happily resolves as a ref, so every guard written aroundbranchpasses and the command runs against the wrong thing.What I hit, with wt v0.76.0. An alias tidies a merged feature worktree and deletes its remote branch:
Run from a worktree created with
git worktree add --detach ../repo.scratch origin/main,branchrenders asHEAD, sobranch != default_branchpasses;origin/HEADis a symbolic ref in any ordinary clone, so the existence check passes too; and the alias ends with:Smallest repro:
-vprintsbranch = HEADin the template-variables block and the alias echoes[HEAD].The fallback is deliberate:
CommandContext::branch_or_headsubstitutesHEADwhen there is no branch, and the hook pipeline matches it. Expected instead is one of:branchunset in a detached worktree, so{% if branch %}guards it the way the docs already prescribe forupstream, and it agrees with the JSON output. This is the one I would pick.detachedvariable alongside it.The workaround today is
{% if branch != "HEAD" %}, which holds only because git refusesHEADas a branch name, and nothing documents thatHEADis what a detached worktree renders.Seen on worktrunk bc2ec12.