Skip to content
Development
Skill

/post-merge-cleanup

Finish an already-merged branch. Proves the branch is contained in the fetched default, classifies leftover artifacts as unique / redundant / superseded, returns to a clean --ff-only default checkout, and deletes the merged local branch — every discard confirmed per item. Routed

From plugin
codearbiter
14562 skills19 agents42 commands
Install
$ npx -y skills add arbiterForge/codeArbiter --skill post-merge-cleanup --agent claude-code

How it fires

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

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/post-merge-cleanup

Context preview

The summary Claude sees to decide when to auto-load this skill.

Finish an already-merged branch. Proves the branch is contained in the fetched default, classifies leftover artifacts as unique / redundant / superseded, returns to a clean --ff-only default checkout, and deletes the merged local branch — every discard confirmed per item. Routed

SKILL.md

post-merge-cleanup.SKILL.md
name: post-merge-cleanup
description: Finish an already-merged branch. Proves the branch is contained in the fetched default, classifies leftover artifacts as unique / redundant / superseded, returns to a clean --ff-only default checkout, and deletes the merged local branch — every discard confirmed per item. Routed to by {{CMD:cleanup}}.

post-merge-cleanup

The walk back from a merged PR. Routed to by `{{CMD:cleanup}}`.

The work has landed on the default branch. What is left is local: the branch you are standing on, and whatever the run left in the tree. This skill returns you to a clean, fast-forwarded default checkout without losing anything you have not explicitly agreed to lose.

**It is ordinary lifecycle work and never needs `{{CMD:override}}`.** When it cannot proceed, it names the gate that stopped it. Issue #308 recorded the alternative: with no owner for this transition, the routing loop reached for `{{CMD:chore}}`, then for `{{CMD:override}}` — a bypass manufactured to cover a coverage hole.

Pre-flight

Read this, or STOP and surface the gap — never guess the default-branch name:

  • `{{PROJECT_DIR}}/.codearbiter/CONTEXT.md` — the default-branch name.

If HEAD is already the default branch, there is no transition to make. Report the dirty state read-only and exit; branch pruning across *other* branches belongs to `{{CMD:standup}}`.

Phase 1 — Prove the merge · gate: BLOCK

A branch that looks merged is not merged. Establish it against the network, not against a stale local ref. This repo squash-merges by default, so SHA-ancestry alone is the wrong instrument for most landings: it holds for a fast-forward or a merge-commit landing, but a squash merge writes a new commit with no SHA lineage back to the branch, and `--is-ancestor` will report non-zero for a branch that landed cleanly. The gate is **content-containment**, proven by whichever instrument fits the landing, always reported as a fact:

1. `git fetch` the remote holding the default branch. A fetch that fails STOPs — an unfetched comparison proves nothing. 2. Prove containment with two primary instruments plus a fallback, tried in order: 1. **Ancestry** — `git merge-base --is-ancestor HEAD origin/<default>`. Holds for a fast-forward or merge-commit landing. If it holds, that is the proof: report it and move on. 2. **Squash-merge proof via the PR record** — if ancestry fails, run `gh pr list --head "$(git branch --show-current)" --state merged --json number,state,headRefOid,mergeCommit`. A `MERGED` PR whose `headRefOid` equals local `git rev-parse HEAD` proves every commit on this branch rode that PR's squash into the default branch. Report the PR number, that `headRefOid == HEAD`, the merge commit (`mergeCommit.oid` in that JSON), **and** the ancestry check's negative result — all as facts, not as a failure. Corroborate with `git diff --quiet origin/<default> HEAD` when it happens to hold; a **non-empty** diff alongside a valid PR proof is normal (the default branch advanced since the merge landed) and is reported as a fact, never treated as a failure. The PR-record identity — `MERGED` plus `headRefOid == HEAD` — is the load-bearing proof; the diff is corroboration only when it happens to be fresh, never a requirement. 3. **Fallback when `gh` is unavailable or no PR record exists** — `git diff --quiet origin/<default> HEAD` (the tree is byte-identical to the fetched default) is an acceptable fallback proof. Report it as such. 3. Report whichever instrument held as a fact — the default branch, the fetched SHA, and which of the three proofs established containment.

If none of the three hold, STOP exactly as today. Name the un-landed commits and route to `{{CMD:pr}}`; nothing is deleted here.

> Deliberate deviation from issue #586's suggested contract: that suggestion > required the squash-merge proof's diff to be empty. That requirement > re-breaks this gate the moment any later PR merges to the default branch — > the common state, not an edge case. The PR-record identity is what proves > containment; the diff is corroboration, demoted from requirement to fact.

Gate: the remote is fetched and HEAD is proven contained in the fetched default branch — by ancestry, by the PR-record squash proof, or by the byte-identical fallback — or the skill has stopped.

Phase 2 — Classify the residue · gate: BLOCK

List every dirty tracked change, every untracked file, and every stash reachable from this branch. Classify each into exactly one of three, and say *why* for each:

  • **Redundant** — byte-identical to content already on the default branch, or a

regenerated build artifact whose generator is committed and rerunnable. Safe to remove because removing it loses no information.

  • **Superseded** — an earlier form of something the merged PR already landed in a

better shape. Name what supersedes it.

  • **Unique** — anything else. This is the default: an artifact that cannot be

proven redundant or superseded IS unique. Uncertainty classifies as unique, not as redundant.

Present the classification before touching anything. A file whose class you cannot establish is reported as unique with the reason you could not classify it.

Gate: every dirty, untracked, and stashed artifact carries a class and a stated reason, with unclassifiable items counted as unique.

Phase 3 — Resolve the residue · gate: STOP

Per item, in the order Phase 2 listed them. Never batch:

  • **Unique** — offer to keep it: `{{CMD:commit}}` it on this branch before the

transition, move it aside, or leave it in place and stop the cleanup. It is discarded only if the user explicitly confirms *that item by name*, with the Phase 2 reasoning in view.

  • **Redundant / superseded** — offer removal, one confirmation each, stating what

it is and why it is safe. Declining leaves it exactly where it is.

A stash is never dropped

Read more
Ships withcodearbiter

When you can't trust yourself with your code base, trust Arbiter.

Get the whole plugin

Other skills on codearbiter.