safe-cleanup
Use to safely clean up stale LOCAL feature branches left behind by the TDD harness once their slices have shipped — both merged branches and abandoned (closed-PR) branches. Phase 5 maintenance and the cleanup companion to safe-pr. It reports first, confirms before deleting, never force-deletes unmer
Install
mkdir -p .claude/skills/safe-cleanup && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16793" && unzip -o skill.zip -d .claude/skills/safe-cleanup && rm skill.zipInstalls to .claude/skills/safe-cleanup
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Use to safely clean up stale LOCAL feature branches left behind by the TDD harness once their slices have shipped — both merged branches and abandoned (closed-PR) branches. Phase 5 maintenance and the cleanup companion to safe-pr. It reports first, confirms before deleting, never force-deletes unmerged work, never touches the remote, and never deletes main, the current branch, or any branch with an open PR. Trigger on 'clean up merged branches', 'delete stale feature branches', 'tidy up branches', 'prune local branches', or after a PR merges. Records every deleted branch's SHA so anything is recoverable.About this skill
Safe Cleanup — Tidy Stale Local Branches (Phase 5)
Remove the per-slice feat/<feature>/<NN>-<slice> branches that pile up after slices ship — safely. Same posture as safe-pr: report first, confirm before deleting, never force away unmerged work, keep everything recoverable. Scope is LOCAL branches only by design; this skill never deletes anything on the remote.
The deterministic classification (cross-referencing git merge status and gh PR state) lives in scripts/classify-branches.mjs. It defaults to dry-run and deletes nothing without --apply --yes plus the category you approve.
What counts as what
| Bucket | Meaning | Default action |
|---|---|---|
| merged | Commits already in the base (ancestor), or PR merged and git cherry confirms every commit is present in the base | Eligible to delete (after you confirm) |
| ahead-of-merged-pr | PR merged, but the branch carries extra commits not in the base (reused branch name, or commits pushed after the merge) | Never delete — report only |
| abandoned | PR was closed without merging — carries commits not in the base | Delete only if the user explicitly opts in; recoverable via reflog / the log only |
| open-pr | Has an open PR — active work (wins even if the branch is an ancestor of the base) | Never delete |
| local-only | Unmerged local commits, no PR (possible WIP) | Never auto-delete — report only |
| protected | base / main/master/develop/release, the current branch | Never delete |
If gh or the remote is unavailable, PR-based buckets (squash-merged, abandoned) can't be detected — only git-merged branches are eligible, and the report says so. That is the safe default.
Procedure
-
Freshen
mainfirst (so "merged into main" is accurate). If there's a remote,git fetch originand fast-forwardmain. Then run from the repo root, ideally withmainchecked out (the current branch is always protected, so checking outmainlets a feature branch become eligible). The script refuses to run on a detached HEAD, since the current branch must be well-defined to protect it. -
Dry-run report. Run the classifier — it deletes nothing:
node "${CLAUDE_SKILL_DIR}/scripts/classify-branches.mjs"Show the user the table and the summary (how many merged / abandoned / kept).
-
Confirm. Explain the buckets in plain terms:
- The merged branches are safe to delete — their work is in
main(or their PR merged). - The abandoned branches carry commits that are not in
main; deleting them drops that work (recoverable only viagit reflogfor a limited window). Only proceed with these if the user explicitly says so. - open-pr and local-only branches are kept untouched. Get an explicit go-ahead for the merged set, and a separate explicit go-ahead for the abandoned set if the user wants those gone too.
- The merged branches are safe to delete — their work is in
-
Apply. Delete only the approved categories, writing a recovery log:
node "${CLAUDE_SKILL_DIR}/scripts/classify-branches.mjs" --apply --yes --delete-mergedA recovery log (
.tdd-branch-cleanup.logat repo root by default, or--log <path>) is written before any deletion, and the run aborts if it can't be written. Add--delete-abandonedonly if the user approved removing abandoned branches. Add--protected name1,name2to shield extra branches, or--base <branch>if the base branch isn't auto-detected. -
Report. Tell the user which branches were deleted and surface the recovery block (each
branch → SHA). Restoring any of them is justgit branch <name> <sha>(or viagit reflog). The recovery log persists this for later. -
Optional bookkeeping. If a deleted branch's slice plan still says in-progress, update
plans/<feature>/<NN>-<slice>.md/ the feature README to reflect that the slice is merged and its branch cleaned.
Safety rules (non-negotiable)
- Local only. Never run
git push origin --deleteor otherwise touch remote branches from this skill. Removing a shared remote branch is a deliberate, separate action the user must drive themselves. - Never delete
main/the default branch, the current branch, protected branches, or any branch with an open PR. - Never force-delete unmerged local work (
local-onlywith unique commits) — report it and let the user decide per-branch. - Use the safe delete (
git branch -d) for git-merged branches; fall back to-Donly for branches confirmed merged via their PR, or for abandoned branches the user explicitly approved — and always log the SHA first. - Dry-run is the default. Never pass
--applyuntil the user has seen the report and confirmed. - Merged-safe means genuinely merged. A branch is only treated as deletable-merged if its commits are truly in the base — an ancestor, or
git cherryconfirms every commit is patch-present. A branch with extra commits beyond its merged PR is kept, never force-deleted (this defeats branch-name reuse and post-merge commits). Force-deletes are re-verified at the moment of deletion. - Refuses to run on a detached HEAD, so the current branch is always well-defined and protected.
- Confirm before deleting, exactly as
safe-prconfirms before pushing.
When to run
After PRs merge — e.g. straight after safe-pr reports a merge, or periodically to tidy up. It pairs with safe-pr: safe-pr opens the PR for a slice; safe-cleanup retires the branch once that PR is done. You can also schedule it with the loop or schedule skills if you want routine tidying.