Skip to content

clean --gone offers to delete the default branch #19

Description

@leogdion

Labels: bug
Priority: blocker
Source: claude

Summary

The --merged pass of clean explicitly protects the default branch. The
--gone pass does not. If the default branch's upstream is reported [gone],
clean --gone --apply will remove its worktree and delete the branch.

Evidence

  • Merged pass guards it — git-trees:472: | grep -vx "$def"
  • Gone pass has no equivalent — git-trees:429-448

Reproduced with a file:// fixture where the upstream main was deleted so
local main reports [gone] after fetch --prune:

$ git for-each-ref --format='%(refname:short) %(upstream:short) %(upstream:track)' refs/heads
doomed origin/doomed [gone]
main origin/main [gone]

$ git trees clean --gone
== branches with gone upstream ==
  doomed  (/…/proj/doomed)
  main  (/…/proj/main)
(report only — pass --apply to execute)

With --apply, main gets git worktree remove followed by git branch -d.

Mitigations that limit but do not remove the impact: clean reports unless
--apply is passed, and git branch -d refuses unmerged branches. But when the
default branch is fully merged (the common case for a synced main), -d
succeeds and the worktree is already gone by then. Of everything found in this
review, this is the only issue with real blast radius.

Plausible ways to reach [gone] on a default branch: the remote's default was
renamed (mastermain), the branch was deleted and recreated upstream, or
a fork's default differs from the local checkout.

Proposed fix

Skip $def in the gone loop, mirroring line 472. def is already in scope
(assigned at git-trees:426):

while read -r br; do
  [ -z "$br" ] && continue
  [ "$br" = "$def" ] && continue   # never touch the default branch
  ...

Consider also skipping the branch checked out in the current worktree. The
--older-than pass already protects cwd (git-trees:486-490); the gone and
merged passes do not.

Test plan

  • Fixture where the default branch's upstream is gone → neither the report
    nor --apply touches it
  • Non-default gone branches are still reported and still removed under
    --apply (no regression)
  • --merged behavior unchanged
  • CI covers default-branch protection on the gone pass

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions