Skip to content

Alias and hook templates render {{ branch }} as HEAD in a detached worktree #4009

Description

@max-sixty

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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions