Skip to content
Back to skills

Branch Archive

ASecurity

Safely retire a local branch by tagging its tip first, then deleting the branch. The sanctioned escape hatch from guard-destructive.sh's blanket `git branch -D` block — turns silent-loss-of-unmerged-work into a reversible, audit-logged operation. Use when an agent or maintainer wants to clean up old feature branches that represent abandoned approaches, stale plans, or superseded work.

  • 7 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentsgobashawsgit

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add mcorbett51090/RavenClaude --skill branch-archive --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Branch Archive?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Branch Archive
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mcorbett51090-branch-archive/badge)](https://www.skillsdirectory.com/skills/mcorbett51090-branch-archive)

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

Download with Pro
SKILL.md
---
name: branch-archive
description: "Safely retire a local branch by tagging its tip first, then deleting the branch. The sanctioned escape hatch from guard-destructive.sh's blanket `git branch -D` block — turns silent-loss-of-unmerged-work into a reversible, audit-logged operation. Use when an agent or maintainer wants to clean up old feature branches that represent abandoned approaches, stale plans, or superseded work."
---

# Branch archive — the safe path past `guard-destructive`

The marketplace's `guard-destructive.sh` hook blocks `git branch -D` blanket because it can't tell *valuable unmerged work* from *stale planning state* — both look identical to a regex. The block is correct policy: an agent who force-deletes the wrong branch can silently lose hours of work, and there's no observable trace.

This skill is the sanctioned escape hatch. The pattern:

1. **Tag the branch tip** (`archive/<branch>-<timestamp>`) — the work survives forever, recoverable via `git checkout <tag>`.
2. **Push the tag to `origin`** — durable across machines, survives the Codespace going cold.
3. **Write an audit-log entry** with reason + evidence + the unmerged-vs-main commit list — the *why* is recorded, not just the *what*.
4. **Delete the local branch** via `git update-ref -d refs/heads/<branch>` — the lower-level primitive the guard doesn't pattern-match (correctly, because the script's preconditions are what make this sound).
5. **Optionally delete the remote branch** if `--delete-remote` is passed and the branch is still on origin.

The script aborts before step 4 if any of steps 1-3 fail. There is no "force" variant — the safety preconditions are what make the script's use of the lower-level primitive defensible. **Do not use `git update-ref -d` directly to bypass the guard for any other reason.**

## When to use

- An old feature branch whose work was folded into another PR via a squash merge (the branch's commits aren't on main as identical SHAs).
- A planning / spike / WIP branch whose approach was superseded and the team chose a different path.
- A branch whose remote was deleted but a local copy lingers.
- A long-running branch that's drifted to the point where rebasing costs more than redoing the work in a fresh branch (record the abandonment with evidence).
- Any branch where the answer to "what would we lose if this disappeared?" is "nothing of remaining value — and we have a corroborating reference for that."

## When NOT to use

- The work on the branch is genuinely needed and unmerged. **Open a PR or rebase first.**
- You're not sure whether the work is needed. **Check the patch-IDs against main first** (`git log <branch> --not main`), open the PR list, ask. Don't archive what you don't understand.
- The branch is currently checked out. The script refuses; `git checkout main` first.
- The branch is `main` / `master` / `HEAD`. The script refuses.
- A branch the team is collaborating on. Archive is for solo / abandoned work; multi-author branches warrant a team conversation first.

## Invocation

```bash
# Shipped path (canonical, inside the plugin) — invoked via `bash` so no execute bit is required:
bash "${CLAUDE_PLUGIN_ROOT}"/scripts/archive-branch.sh <branch> --reason "<one-line why>" [options]
# If ${CLAUDE_PLUGIN_ROOT} is unset (some hosts), resolve from the plugin cache (mirrors bin/rc):
#   bash "$(find ~/.claude/plugins/cache -name archive-branch.sh -path '*ravenclaude-core*' | head -1)" ...
```

| Option | Effect |
|---|---|
| `--reason "..."` | **Required.** One-line justification. Records to the audit log. |
| `--evidence "..."` | Recommended. PR number, decision doc, commit SHA, anything that lets a future reader verify the reason. |
| `--skip-push` | Don't push the tag to origin (offline / disconnected work). The work is recoverable locally; lost if the machine dies. |
| `--delete-remote` | Also delete the branch on `origin` if it's still there. |
| `--yes` | Skip the final "Proceed?" confirmation. Use only when scripted. |

The script always prints a plan first (branch, tip SHA, commits about to be archived, archive tag name, reason, evidence, operator), and unless `--yes` is passed, asks `Proceed? [y/N]`. The default is **No**.

## Recovery

The archive tag is durable. To resume work on the archived branch:

```bash
git checkout archive/<branch>-<date>       # detached HEAD at archived tip
git switch -c <new-branch-name>             # recreate a working branch
```

The tag is on `origin`, so the recovery works on any machine — the work isn't tied to one Codespace.

## Worked example

PR #221 (`fix/install-ergonomics-copilot-path`) merged a Copilot-bridge ergonomics fix to `main`. A stale local branch `fix/copilot-installer-status-project-flag` from May 27 carried three commits whose content overlapped with PR #221 — patch-ID check vs main was inconclusive (the squash flattened the commits), but reading `plugins/ravenclaude-core/CLAUDE.md` confirmed the feature lives on main.

```bash
scripts/archive-branch.sh fix/copilot-installer-status-project-flag \
  --reason "Stale May 2026 branch; remote was deleted; current Copilot bridge state on main supersedes." \
  --evidence "Commits 81c3a92 + 0358b2a (patch-id checked vs main: not present, but feature lives in main's Copilot bridge — confirmed via plugins/ravenclaude-core/CLAUDE.md)" \
  --yes
```

Result: tag `archive/fix-copilot-installer-status-project-flag-2026-06-03-002017` pushed to origin; audit log at `.ravenclaude/runs/branch-archive/2026-06-03-002017-fix-copilot-installer-status-project-flag.log`; local branch gone; work fully recoverable.

## Why this is sound

The marketplace's destructive-action discipline (in [`plugins/ravenclaude-core/CLAUDE.md`](../../CLAUDE.md) and [`AGENTS.md`](../../../../AGENTS.md)) draws a line between **irreversible** and **reversible** operations. `git branch -D` is irreversible — once executed, the branch's commits are no longer referenced and will be garbage-collected. `archive-branch.sh` is reversible — the tag is a permanent reference, the audit log captures the reasoning, and the recovery path is documented.

`guard-destructive.sh`'s `git branch -*D*` regex is blanket because the guard can't read intent. This skill's contract — tag first, push tag, audit-log, then delete via the lower-level primitive — encodes the intent the regex can't infer. Bypass via `git update-ref -d` *outside* this script is not sanctioned; the script's preconditions are what make its use of that primitive defensible.

## Routing

| When | Who | What |
|---|---|---|
| Agent wants to clean up old branches at end-of-session | The agent itself | Run the script with reason + evidence; default to `--no` confirmation prompt unless the agent is sure |
| Maintainer doing repo hygiene | The maintainer | Run the script; recover via the tag if a wrong call is later spotted |
| Bulk cleanup (>5 branches) | The agent | Use the script per-branch (not a wrapper that batches) — each archive needs its own reason + evidence |
| A real-work branch was archived in error | Anyone | `git checkout <tag>` → `git switch -c <branch>` → push. The recovery is normal-Git, no special tooling needed |

## Provenance

- Sanctioned 2026-06-02 after the agent hit the guard during routine branch cleanup. The branch in question (`fix/copilot-installer-status-project-flag`) had real unmerged commits that *should* have been preserved — the guard was correct to block, and the archive pattern is the right next step instead of bypass.
- Script: [`${CLAUDE_PLUGIN_ROOT}/scripts/archive-branch.sh`](../../scripts/archive-branch.sh) (ships inside the plugin; a thin shim at the marketplace-root `scripts/` keeps the marketplace's own references resolving).
- Pattern: tag-first / audit-log / lower-level-delete-primitive. Mirrors the pattern from [`reset-plugin-cache.py`](../../scripts/reset-plugin-cache.py) (snapshot before mutate; atomic swap; recoverable rollback).
- Not (yet) wired into audit-gates — script's behaviour is well-bounded but a future enhancement could ship `bad-attempt-without-reason.fixture` + `good-with-all-args.fixture` for bidirectional verification.

Attribution

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

Loading comments…