Skip to content
Security
Command

/git-cleanup

Safely analyzes and cleans up local git branches and worktrees, categorizing them as merged, squash-merged, superseded, or active work before deleting anything.

From plugin
trailofbits-skills
7.1k8 skills30 agents8 commands2 MCP
Install
> /plugin marketplace add trailofbits/skills

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/git-cleanup

Context preview

What this command does when you run it.

Safely analyzes and cleans up local git branches and worktrees, categorizing them as merged, squash-merged, superseded, or active work before deleting anything.

Command definition

git-cleanup.md
description: "Safely analyzes and cleans up local git branches and worktrees, categorizing them as merged, squash-merged, superseded, or active work before deleting anything."
argument-hint: "[repo-path]"
disable-model-invocation: true
allowed-tools: Bash Read AskUserQuestion Workflow

Git Cleanup

Clean up accumulated git worktrees and local branches. A dynamic workflow gathers the evidence in parallel and tries to disprove its own delete recommendations; you keep the two safety gates and run the deletions yourself.

Repository: `$ARGUMENTS` — when empty, use the current working directory.

Core Principle: SAFETY FIRST

**Never delete anything without explicit user confirmation.**

The split between the workflow and this session is the safety property, not an implementation detail:

| Runs in the workflow (subagents) | Runs here (main session) | |----------------------------------|--------------------------| | Read-only inspection of git state | Both user gates | | Merge-evidence investigation | Every `git branch -d/-D` | | Refutation of delete candidates | Every `git worktree remove` |

Workflow agents run in the background with no way to reach the user. Nothing destructive may move into the script — if it did, deletions would happen while the user was still being asked about them.

Phase 1: Run the Analysis Workflow

`${CLAUDE_PLUGIN_ROOT}` is set in the Bash tool's environment, not in this prompt's text — nothing expands it for you here. **Resolve it first**, in the same call that finds the repo root:

echo "$CLAUDE_PLUGIN_ROOT"
git rev-parse --show-toplevel

Then call the `Workflow` tool with `scriptPath` set to `<that plugin root>/workflows/analyze-branches.js` and this as `args`, both values substituted rather than passed as the literal `${CLAUDE_PLUGIN_ROOT}`:

{ "repoPath": "<absolute path to the repo>", "pluginDir": "<that plugin root>" }

This command being invoked is the opt-in that workflow needs.

It runs three phases — survey, investigate, refute — and returns:

| Field | Meaning | |-------|---------| | `deleteCandidates` | Recommended deletions. Each carries `evidence`, the exact `command` (`-d` or `-D`), `worktreePath` when a worktree holds the branch, `group` for related-branch display, and `verifyWith` on `SAFE_TO_DELETE` entries. | | `needsReview` | Remote gone, work not found in the default branch. Never recommend these. | | `keep` | Unpushed, local-only, or synced with a live remote — plus `PROTECTED` entries, excluded from analysis but still reported. | | `worktrees` | Path, branch, `dirty`, `dirtyFiles`, and whether the branch is stale. | | `unanalyzed` | Branches no verdict came back for. **Must be shown to the user.** |

**Exit criteria:** you hold a result object, or the workflow threw.

If it throws with "survey returned zero local branches", the inventory failed — say so and stop. Do not report a clean repository.

If it throws about an unreadable `scriptPath`, the path did not resolve — most likely `$CLAUDE_PLUGIN_ROOT` was empty or reached the tool unexpanded. Check what the `echo` above printed, and if there is no usable plugin root, take the inline fallback below rather than aborting the run.

**Fallback.** If the `Workflow` tool is unavailable, do the same analysis inline: read [merge-evidence.md](../references/merge-evidence.md), gather the state below, and apply the decision table in [Phase 2](#phase-2-check-the-workflows-work). It is slower and the refutation pass is on you, but the categories and the gates are identical.

# Assign, then default with ${:-}. Do not fall back with `... | sed ... || echo main`:
# a pipeline's status is the last command's, sed succeeds on empty input, so the ||
# never fires and default_branch ends up empty — every command below then silently
# operates on "".
default_branch=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null)
default_branch="${default_branch#origin/}"
default_branch="${default_branch:-main}"
git fetch --prune
git branch -vv                        # tracking info and [gone] markers
git branch --merged "$default_branch"
git worktree list --porcelain
git log --oneline "$default_branch" | grep -iE "#[0-9]+" | head -40

Phase 2: Check the Workflow's Work

The workflow reports evidence so you can audit it, not so you can forward it unread. Before building the gate-1 table:

1. **Every `deleteCandidate` names specific evidence** — a PR number, a commit sha, or a superseding branch. "Similar name", "looks stale", or an empty evidence string is not a delete recommendation. Move it to needs-review. `SAFE_TO_DELETE` entries satisfy this by naming the tip commit; they also carry `verifyWith`, which phase 3 runs before deleting. 2. **No protected branch is a delete candidate.** The script filters long-lived integration and environment names (`main`, `master`, `develop`, `dev`, `staging`, `production`, `qa`, `uat`, `release/*`, `hotfix/*`, and similar), plus the repository's actual default branch and the current branch, programmatically. They do still appear — under `keep` with category `PROTECTED`, and with their unpushed count when they have one. That is deliberate: excluded from analysis is not the same as absent from the report, and a `staging` branch carrying unpushed commits must not vanish. If one reaches `deleteCandidates` or `needsReview` regardless, drop it and say so. 3. **`unanalyzed` is empty, or you list it.** A partial run must not read as a complete one. 4. **Dirty worktrees are flagged**, whatever their branch's category.

The categories, and what has to be true for each:

| Category | Meaning | Delete Command | |----------|---------|----------------| | SAFE_TO_DELETE | Reported by `git branch --merged`, re-checked by `verifyWith` at execution | `git branch -d` | | SQUASH_MERGED | Work incorporated via squash merge, PR or commit named | `git branch -D` | | SUPERSEDED | Work verified in main via PR, or contained in a named newer branch

Read more
Ships withtrailofbits-skills

A Claude Code plugin marketplace from Trail of Bits providing skills to enhance AI-assisted security analysis, testing, and development workflows. Codex can load this marketplace through its Claude marketplace compatibility.

Get the whole plugin

Other commands on trailofbits-skills.