Skip to content

feat: sweep merged worktree branches that outlived their worktree - #51

Merged
xantorres merged 1 commit into
mainfrom
feat/worktree-branch-sweep
Aug 22, 2026
Merged

xantorres merged 1 commit into
mainfrom
feat/worktree-branch-sweep

Conversation

@xantorres

Copy link
Copy Markdown
Owner

Releasing a worktree removes the directory but leaves its branch behind, so every completed epic and sprint deposits a ref that nothing ever collects.

Adds rk worktree sweep --preview | --apply: lists or deletes local branches under the configured worktrees.branchPrefix that are fully merged into worktrees.baseBranch and no longer back a checked-out worktree. Leaked worktree directories stay with rk fix.

Deletion deliberately avoids git branch -d, which measures the merge against HEAD rather than against the base the listing used: a branch merged into main is refused whenever HEAD sits on a divergent branch, so preview would list refs that apply then rejected. It re-runs the listing's own ancestry check immediately before removing the ref instead, keeping the guard against a ref that moved between listing and deletion while measuring it against the ref the decision was made on.

Test plan

  • 8 integration tests over real git: both mode guards, preview, apply, unmerged branch untouched, out-of-prefix branch untouched, branch still backing a worktree untouched, and deletion from a divergent HEAD (that one reproduced the branch -d bug before the fix).
  • Full suite green: 1602 tests, biome check, typecheck, build, coverage gate, and the fallow quality gate under CI's coverage.
  • End to end on a scratch repo: rk close T-001 leaves rk/epic/E-001 behind; sweep lists it with its SHA and deletes it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6129f33030

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/cli/src/lifecycle/worktree.ts Outdated
if (!branch || !head) continue;
if (!branch.startsWith(prefix)) continue;
if (attached.has(branch)) continue;
if (branch === base) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Exclude the configured local base branch

When worktrees.baseBranch itself starts with branchPrefix and origin/<baseBranch> exists, base is the remote-tracking name, so this comparison does not exclude the corresponding local base branch. If that local branch is not checked out and is contained in the remote ref, --apply deletes it; subsequent worktree acquisition then cannot find the configured local base and may fall back to the caller's current HEAD. Compare against config.worktrees.baseBranch as well as the resolved merge-check ref.

Useful? React with 👍 / 👎.

// try the next candidate
}
}
return baseBranch;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle an absent configured base explicitly

When neither origin/<baseBranch> nor the local base exists, returning the unresolved name makes every isAncestor call quietly return false, so sweep reports success with no candidates. This conflicts with the existing worktree creation path, which deliberately falls back to the current branch when the configured base is absent; branches created and later merged through that path can therefore never be swept. Reuse the lifecycle fallback or return a runtime error instead of silently treating the repository as clean.

Useful? React with 👍 / 👎.

Releasing a worktree removes the directory and its record but leaves the
branch behind, so every completed epic and sprint deposits a ref that
nothing ever collects.

Add `rk worktree sweep`, which lists or deletes local branches under the
configured prefix that are fully merged into the base and no longer back
a checked-out worktree. One of --preview or --apply is required; a bare
`sweep` has no safe default reading. The base resolves to the
remote-tracking copy when one exists, since a stale local main would
leave finished branches looking unmerged.

Deletion re-runs that ancestry check immediately before removing the ref
rather than relying on `git branch -d`, which measures the merge against
HEAD: a branch merged into the base is refused whenever HEAD sits on a
divergent branch, so preview would list refs that apply then rejected.

Ownership is narrow because the delete is forced. The prefix must match
on a path boundary, so a short `branchPrefix` cannot adopt `release/*` or
`refactor/*`. The base is excluded under both its local and its
remote-tracking name, and again inside the delete itself, so a base that
sits under the prefix never sweeps itself away. A failure to list
worktrees propagates instead of defaulting to an empty set, which would
make every checked-out branch look sweepable. Deleted branches print
their full commit, the only way back from a mistake.

Leaked worktree directories stay with `rk fix`. Two commands able to
remove the same directory under different safety rules is worse than one.
@xantorres
xantorres force-pushed the feat/worktree-branch-sweep branch from 6129f33 to c53ad58 Compare August 22, 2026 07:34
@xantorres
xantorres merged commit 8d501b9 into main Aug 22, 2026
3 checks passed
@xantorres
xantorres deleted the feat/worktree-branch-sweep branch August 22, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant