Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Prune Git Workspace

ASecurity

Clean up the git workspace in one pass — salvage evidence from and prune stale agent worktrees, delete provably-dead local branches by script, run a judgment pass over the unmerged remainder that makes any finding durable before its branch goes, and triage stale remote branches on origin into a deletion script for the user to run. Use when asked to clean up, prune, or delete old branches or worktrees, when a checkout has hundreds of stale local branches, or after a run of agent sessions has l...

8 stars
0 votes
0 copies
0 views
Added 9/29/2026
ai-agentsgoshellbashgit

Works with

mcp

Security Analysis

A100/100

Scanned 10/6/2026

$npx -y skills add KyleMit/Splotch --skill prune-git-workspace --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Prune Git Workspace?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Prune Git Workspace
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kylemit-prune-git-workspace/badge)](https://www.skillsdirectory.com/skills/kylemit-prune-git-workspace)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: prune-git-workspace
description: Clean up the git workspace in one pass — salvage evidence from and prune stale agent worktrees, delete provably-dead local branches by script, run a judgment pass over the unmerged remainder that makes any finding durable before its branch goes, and triage stale remote branches on origin into a deletion script for the user to run. Use when asked to clean up, prune, or delete old branches or worktrees, when a checkout has hundreds of stale local branches, or after a run of agent sessions has left worktrees behind.
---

# Prune git workspace

Agent sessions leave three kinds of debris: linked worktrees, the local branches they were cut on,
and remote branches on `origin` that never got deleted. They are one chore with one hazard set —
concurrent sessions hold some of each, "merged" has more than one meaning, and an unmerged branch
sometimes holds a finding worth more than its code — so this skill handles all three in order.

**Order matters.** A worktree holds its branch, so worktrees go first; local branches second; remote
branches last, because deleting a remote branch turns its local twin into a `gone` upstream the
local pass then catches on a rerun.

Every script below is a **dry run by default** and prints one `<outcome>  <subject>  <reason>` line
per item. Read the plan, then rerun with `-- --apply`. `tools/git-housekeeping/README.md` documents
every guard and outcome word.

## Never touch

The scripts enforce this set and the judgment pass must too:

* `main`, and the branch of the current checkout.
* Any branch checked out in a worktree — including other agents' sessions running right now. Git
  refuses these anyway; the scripts say *which* worktree holds it instead of failing opaquely.
* Any branch with an **open** pull request.
* The main checkout, the worktree you are in, a worktree some process has as its cwd, and a locked
  worktree.
* On `origin`: `pr-assets`, the orphan branch of PR screenshots that always looks like a stale
  outlier.

## Part 1 — Worktrees

`git worktree remove` deletes ignored paths silently, and a clean, merged worktree can still be a
live session's cwd. Salvage first, then prune.

1. `npm run worktrees:salvage` — lists each agent worktree's ignored paths and plans the allowlisted
   evidence (`perf-profiles/`, red-team `decrypted/` and `output/`) out to
   `~/Code/splotch-worktree-evidence/<worktree id>/`, reporting the rest as `leave`. A locked or
   in-use worktree is skipped with its reason and rechecked again at move time, so a capture that
   starts between the plan and the apply keeps its output. Skim the `salvage` rows, then
   `npm run worktrees:salvage -- --apply`. Anything worth more than a stash belongs in
   `docs/scratchpad/` or the scrapbook afterwards, not in that directory forever.
2. `npm run worktrees:prune` — one row per agent worktree under `.claude/worktrees/`,
   `~/.codex/worktrees/`, and `/tmp` (`--root=<dir>` to add or replace). `remove` rows are clean,
   merged into `origin/main`, salvaged, and nobody's cwd. Every `keep` and `skip (in use)` row says
   why; a `keep (uncommitted changes)` is usually another session mid-work — leave it. A worktree
   whose directory is gone is a `keep` row too: its entry holds that worktree's `HEAD` and reflog,
   so the script never runs `git worktree prune`. Leave those entries unless you have checked what
   each holds (`tools/git-housekeeping/README.md` says how to drop one). Then
   `npm run worktrees:prune -- --apply`, which asks every guard again of each `remove` row just
   before removing it and reports a worktree whose `HEAD` moved since the plan as `kept`.

If either pass prints `skip (use unknown)` rows, its process listing failed (`lsof` off `PATH`, or a
shell that may not inspect processes), so it could not tell those worktrees were unused and left
them alone; `--apply` exits 1. The listing is taken again before each row is applied, so rows done
before it failed stay done: read every row. Fix the listing the warning names, then rerun; don't
work around it.

Removing a worktree frees its branch for Part 2, so run Part 1 to completion before planning
branches.

## Part 2 — Local branches

### Scripted pass (no judgment)

`npm run branches:prune` fetches with `--prune`, loads every PR's state through `gh`, and classifies
each local branch:

| Row      | Meaning                                                                         | `--apply` does                                                          |
| -------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `delete` | Tip is an ancestor of `origin/main`                                             | Re-proves it, applies `-d`'s merged check, deletes at the proven commit |
| `proven` | Every commit has a **verbatim** counterpart on main, or a verbatim squash match | Nothing, until `--include-equivalent`                                   |
| `keep`   | Unique commits, or a patch-id match with no verbatim counterpart                | Nothing — the judgment pass below                                       |
| `skip`   | The never-touch set, with the worktree path or PR number                        | Nothing                                                                 |

A plain patch-id match proves nothing here: every patch-id git computes ignores whitespace, and this
repository reformats Markdown, so a reformat branch can match a main it genuinely differs from. The
`proven` rows are re-proved with `--verbatim` patch-ids, and a branch that matches
whitespace-blindly but has no byte-identical counterpart is demoted to `keep` with that reason —
read those rows, they are the interesting ones.

Run `npm run branches:prune -- --apply` for the `delete` tier. Glance at the `proven` rows — each
carries its proof and PR — then `npm run branches:prune -- --apply --include-equivalent`. That flag
is the one place the scripted pass deletes a branch git itself would refuse, and only behind the
script's own two-part proof; it is also the way past a `kept (git branch -d would refuse: …)` row,
which means the branch's upstream (or, with none, the current checkout's `HEAD`) does not hold the
tip yet, not that the branch is unmerged into `origin/main`. Every deletion, in either tier, goes
through `git update-ref -d` at the proven commit id and re-checks the worktree list, so a branch
another session pushed to or checked out since the plan was made is reported as `kept` rather than
destroyed. Without PR state the script plans but refuses to apply — an open PR is in the never-touch
set and cannot be excluded blind.

### Judgment pass (agent)

Everything left is a `keep` row with its reason: `PR #N closed unmerged`, `N unique commits, no PR`,
or `PR #N merged but the branch carries changes its merge commit does not`. Group them by reason,
then for each branch:

1. **Look at the delta**, not the age: `git log --oneline origin/main..<branch>` and
   `git diff --stat origin/main...<branch>`. Decide whether the idea landed some other way (the same
   idea is often explored across several branches and once one lands the siblings are dead), was
   rejected, was abandoned, or is still wanted.
2. **Ask what outlives the code.** This is the point of the pass, not a footnote. A rejected branch
   often holds a finding, a decision, or a measurement — *why* it was rejected, what a reproduction
   looked like, a number nobody wrote down. The worked example: `bump-dprint-markdown-0.23.2`
   carried a rejected 248-file reformat and was deletable only because the reason — dprint 0.23
   wraps inside inline code spans and breaks 670 grep-able command literals — had first been written
   into issue #1634 with a reproduction. Without that, deleting the branch discards the whole
   investigation and the next attempt re-derives it.
3. **Give the finding its durable home first** — an issue or issue comment, an ADR via `create-adr`,
   a `docs/scratchpad/` note, a doc, a code comment; the `self-heal` skill's judgment about which
   home applies. Only then is the branch proposed for deletion.
4. **Present a decide table** — one row per branch: branch · last active · PR state · one-sentence
   summary of the change · durable home written (or "none needed") · recoverable from (`origin`, the
   PR, or **nowhere**) · proposed action. Default to delete; ask the user which to preserve.
5. After approval, delete each with the guarded command, passing the sha you read in step 1:

   ```bash
   npm run branches:prune -- --delete-branch=<name> --at=<sha>
   ```

   Do not reach for `git branch -D <name>` or a raw `git update-ref -d` by hand. Each drops one of
   the two guards this command keeps: `-D` resolves the name again at deletion time, so a commit
   pushed onto the branch while you were writing the table is destroyed without a word, and raw
   `update-ref` deletes a branch another worktree has checked out, leaving that session's `HEAD`
   pointing at nothing. The command refuses both and says which. A branch recoverable from nowhere —
   upstream gone, no PR — gets an explicit per-branch confirmation, since this is the only step in
   the skill that can lose commits.

## Part 3 — Remote branches on `origin`

`npm run branches:gather` prints one row per remote branch, oldest first: `ahead`, `behind`,
`inbase` (`yes` = the tip is on main, or every commit has a byte-identical counterpart there;
**squash merges show `no`** and need the PR check), `age`, `date`, `subject`, and `*` on the current
checkout. Add `--json` for machine-readable rows, `--no-fetch` to skip the round-trip on a rerun.

Sort every branch (minus the never-touch set) into a bucket:

| Bucket                | Test                                                                   | Verdict          |
| --------------------- | ---------------------------------------------------------------------- | ---------------- |
| **A — nothing new**   | `ahead = 0` or `inbase = yes`                                          | Kill             |
| **B — PR resolved**   | `ahead > 0` and its PR is **merged or closed** (catches squash merges) | Kill             |
| **C — stale & moot**  | `ahead > 0`, no open PR, `age > 7d`, and you are confident it landed   | Kill (with note) |
| **D — needs a human** | Anything else — recent, or a real diff with no clear resolution        | **Report, ask**  |

Find PR state in one batch — `gh pr list --state all --json number,state,headRefName` locally, or
the GitHub MCP `search_pull_requests` with `repo:<owner>/<repo> head:<branch>` in a cloud session.
Bucket C follows the same delta reading as the judgment pass above, including the durable-home
question; if you cannot tell, it is bucket D.

Present one consolidated plan: the auto-kill table (A, B, C — branch · age · one-line reason) and
the decide list (D — branch · last active · one-sentence summary, defaulting to kill). Then **hand
back a script; never run the deletion yourself.** A cloud session's git relay returns `HTTP 403` on
ref deletion and the GitHub MCP has no delete-branch tool — that is policy, not a retry — and
deleting a hundred remote refs is outward-facing and irreversible regardless. Write the approved
list to an executable script in the scratch dir and deliver it as a file:

```bash
#!/usr/bin/env bash
# Delete N stale remote branches on origin (triaged by prune-git-workspace).
# Preserved: main, pr-assets, and any open-PR branch. Run from a local clone with push rights.
set -euo pipefail
branches=(
  <branch-1>
  <branch-2>
)
for ((i=0; i<${#branches[@]}; i+=40)); do
  git push origin --delete "${branches[@]:i:40}"   # batched so output stays readable
done
git ls-remote --heads origin | wc -l                # remaining count
```

Tell the user they can spare a branch by deleting its line before running, and that a branch
protection rejection is theirs to resolve. Never emit a script whose contents the user did not
approve.

## Closing the loop

* After the user runs the remote script, `npm run branches:prune` again: the deleted remotes are now
  `gone` upstreams and their merged local twins join the `delete` tier.
* Report the before/after counts for all three surfaces (`git branch | wc -l`,
  `git worktree list | wc -l`, `git ls-remote --heads origin | wc -l`) and every row that was kept
  for a reason the user may want to act on — an in-use worktree belonging to a finished session, a
  `keep` branch that got a durable home but no decision.

Attribution

KyleMitKyleMit
View sourceSee grades on GitHubMore from KyleMit →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Terse caveman voice: answer first, fluff gone, every technical fact kept. Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less tokens". Stays on until "stop caveman" or "normal mode".

1100021 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

698621 votes

Writing Skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dep...

3931 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3421 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Amp, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Grok Build, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

741 votes
View all in ai-agents →