/post-merge-cleanup
Finish an already-merged branch. Proves the branch is an ancestor of 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.
$ npx -y skills add arbiterForge/codeArbiter --skill post-merge-cleanup --agent claude-codeHow 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.
- 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 an ancestor of 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.
SKILL.md
post-merge-cleanup.SKILL.mdname: post-merge-cleanup
description: Finish an already-merged branch. Proves the branch is an ancestor of 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:
1. `git fetch` the remote holding the default branch. A fetch that fails STOPs — an unfetched comparison proves nothing. 2. Confirm the current branch is an **ancestor** of the fetched default (`git merge-base --is-ancestor HEAD origin/<default>`). This is the test that matters, and it is deliberately not `: gone]` upstream state: a squash-merged branch whose remote still exists is fully contained in the default branch and is safe to delete, while a `: gone]` branch whose commits never landed is not. 3. Report the proof as a fact — the default branch, the fetched SHA, and that HEAD is contained in it.
If HEAD is **not** an ancestor, STOP. Name the un-landed commits and route to `{{CMD:pr}}`; nothing is deleted here.
Gate: the remote is fetched and HEAD is proven an ancestor of the fetched default branch — 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 here. Stashes are reported with `git stash show` as the suggested next step, the same report-and-route contract `{{CMD:standup}}` holds.
If anything the user chose to keep would block the checkout, STOP and say so rather than removing it anyway. A blocked checkout is a fine outcome; a silent discard is not.
Gate: every item is resolved by an explicit per-item decision, and the working tree is clean enough to check out the default branch — or the skill has stopped with the blocker named.
Phase 4 — Transition · gate: BLOCK
Only after Phase 3 leaves the tree safe:
1. Check out the default branch, then **verify the checkout actually happened** (`git branch --show-current`). A checkout silently fails when another worktree holds the branch, and every step after this one would otherwise run against the wrong branch. 2. Fast-forward with `--ff-only`. A divergence means the default branch moved in a way this skill will not reconcile: report it and stop. Never a merge commit, never a rebase, never a reset.
Gate: HEAD is confirmed on the default branch by re-read, and the fast-forward either succeeded or was reported as a refused divergence.
Phase 5 — Delete the merged local branch · gate: STOP
Offer deletion of the now-merged local branch, with the Phase 1 ancestry proof restated. One confirmation, naming the branch.
- `git branch -d` only. Never `-D`: the safety check is the point, and if `-d`
refuses, the ancestry proof and the refusal disagree — report both and stop.
- The **remote** branch is never touched. If the user wants it gone, that is
theirs to do or the platform's auto-delete-on-merge to do.
Declining leaves the branch in place. That is a normal outcome, not a failure.
Gate: the branch is deleted only after an explicit confirmation naming it, via `-d`, with the remote untouched.
Phase 6 — Receipt
One short summary: what landed, what was removed, what was kept, and where HEAD is now. State declines as declines — a cleanup the user stopped halfway is a corre
Read more
name: post-merge-cleanup
description: Finish an already-merged branch. Proves the branch is an ancestor of 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:
1. `git fetch` the remote holding the default branch. A fetch that fails STOPs — an unfetched comparison proves nothing. 2. Confirm the current branch is an **ancestor** of the fetched default (`git merge-base --is-ancestor HEAD origin/<default>`). This is the test that matters, and it is deliberately not `: gone]` upstream state: a squash-merged branch whose remote still exists is fully contained in the default branch and is safe to delete, while a `: gone]` branch whose commits never landed is not. 3. Report the proof as a fact — the default branch, the fetched SHA, and that HEAD is contained in it.
If HEAD is **not** an ancestor, STOP. Name the un-landed commits and route to `{{CMD:pr}}`; nothing is deleted here.
Gate: the remote is fetched and HEAD is proven an ancestor of the fetched default branch — 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 here. Stashes are reported with `git stash show` as the suggested next step, the same report-and-route contract `{{CMD:standup}}` holds.
If anything the user chose to keep would block the checkout, STOP and say so rather than removing it anyway. A blocked checkout is a fine outcome; a silent discard is not.
Gate: every item is resolved by an explicit per-item decision, and the working tree is clean enough to check out the default branch — or the skill has stopped with the blocker named.
Phase 4 — Transition · gate: BLOCK
Only after Phase 3 leaves the tree safe:
1. Check out the default branch, then **verify the checkout actually happened** (`git branch --show-current`). A checkout silently fails when another worktree holds the branch, and every step after this one would otherwise run against the wrong branch. 2. Fast-forward with `--ff-only`. A divergence means the default branch moved in a way this skill will not reconcile: report it and stop. Never a merge commit, never a rebase, never a reset.
Gate: HEAD is confirmed on the default branch by re-read, and the fast-forward either succeeded or was reported as a refused divergence.
Phase 5 — Delete the merged local branch · gate: STOP
Offer deletion of the now-merged local branch, with the Phase 1 ancestry proof restated. One confirmation, naming the branch.
- `git branch -d` only. Never `-D`: the safety check is the point, and if `-d`
refuses, the ancestry proof and the refusal disagree — report both and stop.
- The **remote** branch is never touched. If the user wants it gone, that is
theirs to do or the platform's auto-delete-on-merge to do.
Declining leaves the branch in place. That is a normal outcome, not a failure.
Gate: the branch is deleted only after an explicit confirmation naming it, via `-d`, with the remote untouched.
Phase 6 — Receipt
One short summary: what landed, what was removed, what was kept, and where HEAD is now. State declines as declines — a cleanup the user stopped halfway is a corre
Showing the first part of this file.
When you can't trust yourself with your code base, trust Arbiter.
Repo: arbiterForge/codeArbiter
Other skills on codearbiter.
- /brainstorming
The Socratic spec-refinement front of /feature, and the planning front of /sprint. Routed to BEFORE any code — it takes a one-line idea and drives it to an approved, concrete spec with testable acceptance criteria. Four gated phases — frame, refine, write, approve. No
Open skill - /commit-gate
The only path to a commit. Routed to when the user invokes /commit or otherwise instructs codeArbiter to persist staged changes. Nine gated phases — permission, branch, classification, verification (test/lint/secrets), behavioral proof, diff review, selective stage, message,
Open skill - /context-check
Optional manual drift audit — report stale provenance-tracked docs (via _provenancelib drift detection across .codearbiter/.provenance/), then per stale doc offer re-scout / re-baseline / defer. Not the daily loop; commit-gate auto-heal owns routine maintenance.
Open skill - /context-creation
The brownfield back-fill. Routed to by /create-context, and by startup when .codearbiter/CONTEXT.md lacks the <!--INITIALIZED--> body marker but source code exists. Six gated phases — pre-flight, scout dispatch, synthesis, gap interview, write, lock. Reads the existing codebase
Open skill - /crypto-compliance
The banned-primitive gate. Routed to when changed code hashes, signs, encrypts, derives keys, generates security-relevant randomness, configures TLS, or imports a crypto library. Rejects broken primitives, disabled TLS verification, and home-rolled crypto; the approved-primitive
Open skill - /debug
Investigate-then-decide root-cause analysis for a defect whose cause is unknown (distinct from /fix, which assumes a known bug). Five gated phases: capture, hypothesize, gather, decide, hand off. Investigation only, no code changes; exits to /fix, /adr, or a no-action close.
Open skill

