Skip to content

fix: clear error when new branch name collides with existing branch namespace - #3528

Merged
max-sixty merged 2 commits into
mainfrom
fix/issue-3527
Jul 20, 2026
Merged

fix: clear error when new branch name collides with existing branch namespace#3528
max-sixty merged 2 commits into
mainfrom
fix/issue-3527

Conversation

@worktrunk-bot

Copy link
Copy Markdown
Collaborator

Problem

wt switch --create release failed with a raw git error when a branch under the release/ namespace already existed (e.g. release/2026.4):

✗ Failed to create worktree for release from base master
  fatal: cannot lock ref 'refs/heads/release': 'refs/heads/release/2026.4' exists; cannot create 'refs/heads/release'

This is a git ref directory/file (D/F) conflict: git stores refs as file paths under refs/heads/, so release and release/2026.4 can't coexist — one name can't be both a file and a directory. The branch genuinely can't be created, but the git-jargon error didn't make the cause or the remedy clear. Release-style prefixes (release/*, feature/*) are common, so this is easy to hit.

Solution

Detect the collision structurally in the --create failure path and surface a clear, actionable error instead of git's raw text. Detection reads the cached local-branch inventory (local_branches() — no extra subprocess, and only on the error path) and covers both directions of the D/F conflict:

  • creating release when release/2026.4 exists (new name is a prefix), and
  • creating release/2026.4/foo when release/2026.4 exists (an existing branch is a prefix).

New output:

✗ Cannot create branch release — it collides with existing branch release/2026.4
↳ Git stores branches as file paths, so release and release/2026.4 can't both exist. Pick a different name, or rename the existing branch.

Per the repo's "Structured Output Over Error-Message Parsing" guideline, the detection keys on the ref inventory, not on git's locale-dependent "cannot lock ref" prose.

Testing

Added test_switch_create_branch_namespace_conflict in tests/integration_tests/switch.rs, which reproduces #3527 (creates release/2026.4, then wt switch --create release) and snapshots the new error. Verified the fix end-to-end against the issue's minimal reproduction and the reverse-direction case; the normal --create path is unaffected. cargo clippy and the git::error unit tests are clean.


Closes #3527 — automated triage

…amespace

Creating a branch whose name is a path prefix of (or sits under) an
existing branch is a git ref directory/file conflict — 'release' cannot
be created while 'release/2026.4' exists, since git stores refs as file
paths. This surfaced as git's raw 'cannot lock ref' text.

Detect the collision structurally from the cached local-branch inventory
(no extra subprocess) and report a clear, actionable error instead.

Closes #3527
@worktrunk-bot worktrunk-bot added the automated-fix Automated CI fix label Jul 20, 2026
Bold inside a hint closes with the SGR 22m reset, which clears both bold
and the surrounding dim, so the hint text after each branch name lost its
dim styling. Per writing-user-outputs, hints use <underline> (SGR 24m
resets only underline, preserving dim).

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-scoped fix — structural detection over error-text parsing matches the "Structured Output Over Error-Message Parsing" guideline, covers both D/F directions, stays on the error path with no extra subprocess, and the snapshot test reproduces #3527. One observation on the failure attribution, inline.

// refs as file paths, so `release` and `release/2026.4`
// can't coexist. Surface that as a clear, actionable
// error instead of git's raw "cannot lock ref" text.
if *create_branch

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: this fires on any git worktree add failure once a colliding branch exists, so it can mask an unrelated cause with the namespace message. E.g. wt switch --create release --from badref when release/2026.4 exists — git actually failed on the invalid base ref, but we'd report the namespace collision and swallow invalid reference: badref. The path-occupancy and non-create checks are already guarded earlier in validate_worktree_creation, so the realistic remaining case is a bad --from. Narrow, and the reported message is still a real problem the user would hit next, so not blocking — just flagging the unconditional swap of git's error. Could tighten by only substituting when the collision is the plausible cause, but that risks re-introducing error-text parsing, so the current tradeoff may well be the right call.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good flag — I chased down whether the misattribution can actually happen, and for the --base case it can't: --create --base <ref> resolves and validates the base with ref_exists() up front at switch.rs:605, so an invalid base errors with ReferenceNotFound before git worktree add ever runs. Verified end-to-end — wt switch --create release --base badref with release/2026.4 present reports ✗ No branch, tag, or commit named badref, not the namespace collision.

So by the time we reach the failing git worktree add: the path is pre-checked in validate_worktree_creation, the base is either a validated ref or None (→ HEAD, always valid), and --create is set. The only inputs left that can fail there are the D/F collision itself plus rare environmental/race failures — and in the environmental case the swap only fires if a colliding branch also exists, where the namespace conflict is a genuine latent blocker anyway.

I did prototype the tightening you suggested — gate the swap on base_branch.as_deref().is_none_or(|b| repo.ref_exists(b)...) — but since the base is already validated at line 605, its false branch is structurally unreachable, so it'd be speculative dead code (and would trip codecov/patch). Dropped it. The unconditional swap is the right call here; leaving it as-is.

@max-sixty
max-sixty merged commit 56249f9 into main Jul 20, 2026
41 checks passed
@max-sixty
max-sixty deleted the fix/issue-3527 branch July 20, 2026 14:08
@max-sixty max-sixty mentioned this pull request Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated-fix Automated CI fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Failed to create worktree for name used as prefix

2 participants