From wai
Remove git worktrees whose branches have already been merged into the default branch via a closed/merged GitHub pull request. Walks every worktree under the current repo, looks up each branch's merged PR with `gh`, surfaces uncommitted changes, unpushed commits, and untracked planning/spec docs, then asks the user before deleting anything. Optionally archives untracked Markdown notes into the main worktree and offers to delete the now-orphan local branch after the worktree is gone. Use this whenever the user asks to "clean up worktrees", "remove merged worktrees", "prune worktrees", "delete worktrees for merged PRs", "tidy worktrees", "garbage collect worktrees", "which worktrees can I delete", or any variation where they want to know what's safe to remove after PRs land. Strongly prefer this skill over hand-rolling `git worktree list` + ad-hoc `gh pr list` calls when the user wants a per-worktree action breakdown.
How this skill is triggered — by the user, by Claude, or both
Slash command
/wai:cleanup-worktreesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
EVerest-style multi-worktree workflows accumulate `.worktrees/<name>` directories after PRs merge. The branches still exist, but the on-disk checkouts are dead weight, they confuse `git worktree list`, eat disk, and hide stale plan/spec files. This skill walks every worktree, classifies it (still-open / merged / unknown), shows the user what would be lost if it's removed, and only acts after ex...
EVerest-style multi-worktree workflows accumulate .worktrees/<name> directories after PRs merge. The branches still exist, but the on-disk checkouts are dead weight, they confuse git worktree list, eat disk, and hide stale plan/spec files. This skill walks every worktree, classifies it (still-open / merged / unknown), shows the user what would be lost if it's removed, and only acts after explicit confirmation.
The skill is intentionally conservative. Worktree removal can drop unpushed commits and untracked files; the goal is to make the user comfortable that nothing important is being thrown away before any git worktree remove runs.
Trigger phrases include "clean up worktrees", "remove merged worktrees", "prune worktrees", "which worktrees can I delete", "delete worktrees for landed PRs", "tidy worktrees", "garbage collect worktrees". Also auto-trigger when the user says something like "are any of my worktrees ready to remove" or asks for a status table of worktrees.
Recommended cadence: weekly, or after a merge.
Do not trigger for:
The skill is one read-only scan followed by an interactive removal loop:
.md files into the main worktree under the same relative path.git worktree remove [--force] for each confirmed entry.git branch -D <branch>.git worktree prune to clear stale admin records.Never combine steps. Always show the user the table from step 2 and wait for their response before destroying anything.
Run from the main worktree root (use git rev-parse --show-toplevel if uncertain). Use the helper:
bash ${CLAUDE_PLUGIN_ROOT}/skills/cleanup-worktrees/scripts/scan.sh
The script prints a TSV with these columns, one row per non-main worktree:
path<TAB>branch<TAB>pr_number<TAB>pr_merged_at<TAB>uncommitted<TAB>unpushed_count<TAB>untracked_md_count<TAB>state
state values:
| value | meaning |
|---|---|
merged-clean | PR merged, no uncommitted, no unpushed, untracked are only .md notes or empty |
merged-dirty | PR merged, but has uncommitted or unpushed work, needs --force and risks data loss |
merged-archive | PR merged, untracked .md plans/specs present that would be lost without archiving |
open | No merged PR for this branch's head, leave alone |
unknown | gh returned nothing for the branch (no upstream / detached / never pushed); leave alone unless user explicitly opts in |
If gh is not installed or gh auth status fails, fall back to git branch --merged <default-branch> against the upstream of the main branch (git rev-parse --abbrev-ref --symbolic-full-name @{upstream} from the main worktree). Tell the user the fallback is less accurate because squash-merged PRs won't show as merged via plain --merged.
The scan is read-only. It must not run git worktree remove, git branch -D, or git push.
Render the scan output as a Markdown table in the chat. One row per worktree, columns: Worktree, Branch, PR, Merged, Uncommitted, Unpushed, Untracked .md, State. Use the PR number as a link (#1234) if available. Sort by state: merged-clean first, then merged-archive, merged-dirty, open, unknown.
Above the table, say one sentence about what the skill will do next. Below the table, ask the user to confirm. Example:
Found 3 worktrees backed by merged PRs. Default plan: remove all
merged-cleanandmerged-archive(archiving first); skipmerged-dirtyunless you say otherwise. OK?
Stop and wait for the user's response. If they say "go ahead" / "yes" / "ok", proceed with the default plan. If they want a different selection, accept paths or worktree names.
The user response determines TO_REMOVE (list of worktree paths) and INCLUDE_DIRTY (bool). Echo the final plan back as a short list, one line per worktree, before any destructive action. Example:
Removing:
.worktrees/feat-foo(branchfeat/foo, PR #1234), archive 2 .md files first.worktrees/feat-bar(branchfeat/bar, PR #1240), clean, no archive neededSkipping
merged-dirtyworktree.worktrees/feat-baz(3 unpushed commits).
Do not proceed until the user confirms this echo. The user has already approved removal in step 2; this echo exists so they catch fat-finger mistakes (e.g., a typo of feat-baz accidentally included).
For each worktree being removed, mirror any untracked .md files into the main worktree at the same relative path. Skip everything else. This intentionally drops session-state files like .ds/server.pid, but preserves planning/spec/review notes under docs/, thoughts/, tasks/, etc.
Algorithm:
bash ${CLAUDE_PLUGIN_ROOT}/skills/cleanup-worktrees/scripts/archive.sh <worktree-path> <main-worktree-path>
The helper:
git -C <wt> ls-files --others --exclude-standard).*.md matches.cp -n (no-clobber) so existing main-side files are never overwritten.Show the user the per-worktree archive summary before the actual git worktree remove call (e.g., "archived 3 of 3 .md files into main").
For each worktree in the confirmed list:
git worktree remove "<path>"
If that fails because of untracked or modified files (after step 4 archiving, meaning the leftover is genuinely non-Markdown junk like build outputs or session state), and the user confirmed in step 3 that merged-dirty entries should be force-removed, retry with:
git worktree remove --force "<path>"
Never --force without explicit user OK. If the user did not approve force for this row, abort the row, mark it skipped, and continue to the next.
After the loop, run:
git worktree prune
to clear admin records for any worktrees the user removed manually before invoking the skill.
For each successfully removed worktree, ask the user whether to delete the local branch ref. Default answer is no (keep it). Phrase the prompt with the merge target so they can sanity-check, e.g.:
Branch
feat/foo(merged via PR #1234 on 2026-05-19) is no longer checked out anywhere. Delete the local ref?
If yes:
git branch -D <branch>
Use -D (not -d) because squash-merged branches won't pass -d's ancestry check. Do not delete remote refs, that's the PR-cleanup workflow, not this one.
If multiple worktrees were removed, offer a batch confirmation ("delete all of them?") to avoid yes/no spam.
End with a short summary the user can read at a glance:
.md files archived into main.Caveman mode is on for most invocations, keep the summary tight. Fragments OK.
git worktree remove from inside the worktree being removed. Always cd to the main worktree root first or pass an absolute path while at a different cwd.git push anything. This skill is local-only.main write commands. Removing worktrees is allowed on any branch (it's a repo-admin op), but if the user's main worktree HEAD happens to be one of the branches you're being asked to delete, refuse and tell them to check out a different branch first.cp -n only.gh pr list --head <branch> instead of git branch --merged. Plain --merged will miss almost every squash-merged PR.git worktree list will show (detached HEAD) instead of a branch name. The scan classifies them as unknown.origin. gh pr list --head <branch> queries the current repo's origin. If gh returns no result but the user knows the PR was merged elsewhere, treat it as unknown and ask.gh auth: fall back to git branch --merged, warn the user, and exclude any worktree whose branch isn't a strict ancestor of the upstream default branch.git worktree list --porcelain | awk '/^worktree /{print $2; exit}'. The first entry is always the main one.The skill ships three scripts under scripts/:
scan.sh, read-only classification (step 1).archive.sh, mirror untracked .md files into main (step 4).remove.sh, wrapper around git worktree remove that retries with --force only when explicitly told to (step 5). Optional; you can inline the git worktree remove calls instead if it's clearer in context.Read each script before running it the first time so you understand what it touches. They are kept short on purpose.
npx claudepluginhub mlitre/wai --plugin waiGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.