Clean worktrees
Input
$ARGUMENTS
Worktrees accumulate and each consumes disk, so reclaim them once their work has
landed. This command is repo-wide: it cleans every merged worktree in
git worktree list regardless of location (ha/sa/ca and any
other), so a single run reclaims them all. The hard rule: never destroy work
that hasn't been merged. A leftover worktree is cheap; a deleted unmerged
branch is lost work. The main checkout is always preserved; the current worktree
is removed too if its branch is merged.
Context (auto-injected)
- Worktrees: !
git worktree list 2>/dev/null - Merged branches: !
gh pr list --state merged --json number,headRefName --jq '.[].headRefName' 2>/dev/null
Step 1 — Decide the scope
Default is repo-wide. Unless the user named specific feature ids/branches in
$ARGUMENTS, clean every merged worktree in the whole repo — ha, sa, ca,
and any other, regardless of location. Do not narrow to
.claude/worktrees/ha/ just because this is the ha command; a
single run of any of the three cleans them all. Only when $ARGUMENTS names
specific targets do you scope to those.
A worktree/branch is cleanable only if its PR is MERGED (confirm via
gh pr list --state merged, or, after CLAUDE_SKILL_HA_DIR="${CLAUDE_SKILL_DIR}",
bash "$CLAUDE_SKILL_HA_DIR/scripts/merge-check.sh"), or there is no PR and the
user explicitly asked to abandon it. If a PR is still OPEN, skip it and say why.
Step 2 — Remove (delegate to the script — single source of truth)
Don't run removals inline; the script owns the guardrails in one place. With no
$ARGUMENTS, pass all-merged so it sweeps the entire repo; pass a specific
<branch>/name only when the user named one:
CLAUDE_SKILL_HA_DIR="${CLAUDE_SKILL_DIR}"
# no target given → clean EVERY merged worktree repo-wide (ha/sa/ca and any other):
bash "$CLAUDE_SKILL_HA_DIR/scripts/clean.sh" all-merged
# or, only when the user named specific targets:
# bash "$CLAUDE_SKILL_HA_DIR/scripts/clean.sh" "<branch>"
clean.sh considers every worktree in git worktree list (any path), uses
git worktree remove without --force
(uncommitted changes ⇒ skip + report), and git branch -d (never -D). It
never removes the main checkout (it removes the current worktree too, only if
merged), syncs the base branch with origin before deciding, and git worktree prunes at the end — the cleanup guardrails from
superpowers:finishing-a-development-branch.
Two more rules the script owns: a merged worktree still locked by a dead
claude session (claude session ... (pid N) whose pid is gone) is unlocked and
removed — any other lock is an absolute barrier and is only reported. And orphan
directories under .claude/worktrees/ that are no longer registered worktrees
are removed when empty, reported (never deleted) when they still have content.
Step 3 — Report
| feature | branch | PR | action | skipped (reason) |
|---------|--------|----|--------|------------------|
result: cleaned <n> landed features; skipped <m> (unmerged / changes present).