merge-pr · git:20260915.96b4731 · 2026-09-15 · sha256 c9d998ee95ca5145

merge-pr git:20260915.96b4731A

Immutable. This exact content is served forever at /api/v1/blob/c9d998ee95ca5145.

---
name: merge-pr
description: >-
  Land an open GitHub pull request. Use whenever the user wants to MERGE, land, ship, or close out
  an open PR: waits for CI, applies corrections until mergeable (red checks, conflicts with the
  latest `main`, unresolved review), squash-merges, triages follow-ups, and tears down the branch
  and worktree. Triggers: "merge PR 279", "land #281", "ship this PR", "get that PR merged once CI's
  green", "wrap up 279 and open follow-ups", « merge la PR 279 », « fais atterrir la 281 », a bare
  PR link with "merge it". Does NOT apply to opening or implementing a PR, to syncing one STILL
  BEING BUILT (implement-issue), to reviewing without merging (code-review), or to filing an issue
  (create-issue).
license: MIT
compatibility: >-
  Requires an authenticated gh CLI with merge/push rights, and git. Files follow-ups via the
  create-issue skill. Reads the committed repo profile (.claude/skills/repo-profile.md) generated
  by profile-repo.
metadata:
  author: Philippe Matray
  suite: tagout
---

# Merge a pull request

## What this does

`implement-issue` builds a PR and flips it to ready. This skill is the final step: it **lands** that PR
cleanly and cleans up after itself. The job isn't just `gh pr merge` — a PR that's ready isn't
necessarily *mergeable* minutes later, because `main` moves, CI runs, and reviewers leave comments. So
this skill closes the gap: waits for CI, fixes whatever is actually blocking the merge, squashes the PR
in, turns deferred work into tracked issues, and removes the throwaway branch and worktree.

The shape mirrors `implement-issue`'s tail (sync-with-`main`, the profile's conflict hot-spots, the
commit identity) — reuse that machinery. The one new piece is the **corrections loop**: keep clearing
blockers and re-waiting until GitHub reports the PR `CLEAN`, then merge.

## Autonomy contract

Run **hands-off** once started — the user points at a PR and walks away. See
[ADR 0005](../../docs/adr/0005-the-lifecycle-skills-run-hands-off-triage-backlog-does-not.md) for the
decision scope. Whenever a step *could* stop for a question, pick the reasonable default, state the
assumption, keep going. Stop only for a genuine blocker:

- `gh` not authenticated, or no merge/push rights.
- The PR doesn't exist, is already merged/closed, or the number is ambiguous.
- **CI stays red after a real fix attempt.** Don't merge over a red bar, don't disable a failing test, don't `--admin`-override a required check. Fix it for real or stop and show the failing output.
- **A merge conflict you can't resolve with confidence** — both `main` and the branch rewrote the *same logic*. The mechanical conflicts (version, changelog, snapshots, lockfiles) have known-correct fixes (Step 4) — handle those; stop only for genuinely ambiguous ones, showing both sides.
- **A reviewer requested changes you can't satisfy** without guessing intent, or a branch-protection rule you can't legitimately clear (required approvals you can't self-give).
- **The branch has no writable checkout — not the transient sandbox push failure Step 2/§8 already covers — and GitHub reports `mergeStateStatus == DIRTY` or literal `BEHIND`.** The can't-push fallback (Step 4) only substitutes for the self-imposed staleness check (`behind_by > 0` while `mergeStateStatus` still reports `CLEAN`) — it never pushes anything to the PR's real branch, and only a push clears a real conflict or a GitHub-enforced up-to-date gate. That combination is a genuine blocker: stop and report it.

The merge is the irreversible act — earn it. Merge only when CI is **green on the just-corrected
branch** and GitHub reports the PR mergeable; a textual merge of `main` is not a semantic one, so
re-build/re-test after resolving conflicts. Filing a follow-up and deleting a local branch are
reversible — but a follow-up is cheap to *file* and expensive to *carry*, which is why Step 6 triages
before it files.

## Inputs

- **PR identifier** (required) — a number (`279`), an issue/PR URL, or a `gh` PR link. Resolve to a number (Step 1).
- **`--follow-up "<idea>"`** (optional, repeatable) — follow-up work to file as issues after the merge, e.g. `/merge-pr 279 --follow-up "add Rust snapshot tests" --follow-up "document minimap config"`. *Added to* whatever Step 6 discovers in the PR itself.

## Checklist

Create a task per item and work them in order. Step 4 is a loop — repeat until the PR is mergeable.

1. **Preconditions & resolve the PR** — `gh` works, you're in the target repo, normalize the PR number, confirm it's open, capture its head branch + merge state.
2. **Locate (or create) the branch's worktree** — find the local worktree/branch for the PR's head so corrections land in the right checkout; create one tracking the remote branch if none exists.
3. **Wait for CI** — let the checks finish; read the rollup.
4. **Apply corrections (loop)** — clear each blocker the merge state reports (red CI · behind/dirty vs `main` · unresolved review · draft), push, re-wait until the PR is `CLEAN`.
5. **Merge (squash)** — `<kit>/skills/merge-pr/scripts/guarded-pr-merge.sh` once green and mergeable; it runs the merge and decides the outcome from GitHub's `state`, never from the raw `gh pr merge` exit code. If that call is refused (an agent confined to its worktree, the path resolving outside it), see the fallback in [`_shared/guard-invocation.md`](../_shared/guard-invocation.md).
5b. **Read the base's CI run** — the merge just triggered one on `main`; resolve it **by the squash sha**, wait (bounded), and carry the answer into Step 8. Green, red, or an honest non-verdict — never silence.
5c. **Note a decomposed child's landing on its tracking parent** — when the merge closed an issue that is itself a child of a decomposed tracking parent (#315), append one line to the parent's `## Decisions so far` section; a silent no-op for every merge that isn't part of a decomposition.
6. **Triage follow-ups** — gather inline `--follow-up` args + ones discovered in the PR, cluster them by root cause, fold instances into the issue that already owns them, and file at most 3 new issues via `create-issue`.
7. **Delete the local branch & worktree** — from the main checkout, remove the PR's worktree and local branch.
8. **Recap** — the shared closing shape ([`../_shared/recap.md`](../_shared/recap.md), with its [Boundary findings block](../_shared/recap.md#the-boundary-findings-block)): merged PR URL, corrections applied, follow-ups filed, cleanup done.

Resume-safe: re-running mid-flight is fine. If the PR is already merged, skip to Step 5b (recover
the sha from `gh pr view --json mergeCommit`) and then Step 5c and Steps 6–7 — call Step 5c
unconditionally on a resume too, the same way Step 5b's own base-CI read does; its script is
idempotent per PR number, so a second call on an already-noted parent is a no-op, not a duplicate
line. If the
**local** worktree/branch is already gone, skip Step 7's local cleanup — but still run its remote
check (`remote-branch-teardown.sh`): the local branch being gone says nothing about whether
`origin/<headRefName>` survived (#185), and skipping Step 7 outright on a resume is exactly how
that branch leaks unnoticed.

---

## How to read this skill

**One step file at a time, when you reach it — never all up front.** Every token loaded here is
re-read on every later turn (`skills/auto-dev/references/token-economics.md`: ~83% of a run's
spend is context re-read), so the step bodies live under `references/steps/` and the checklist
above is the whole of what loads with the skill. Open a step when its checklist item starts; the
shared references it names load the same way, from inside that step.

- Step 1 — [`references/steps/01-preconditions.md`](references/steps/01-preconditions.md) · reads [`_shared/preconditions.md`](../_shared/preconditions.md)
- Step 2 — [`references/steps/02-worktree.md`](references/steps/02-worktree.md) · reads [`_shared/guard-invocation.md`](../_shared/guard-invocation.md), [`_shared/worktree-ignore-check.md`](../_shared/worktree-ignore-check.md)
- Step 3 — [`references/steps/03-wait-for-ci.md`](references/steps/03-wait-for-ci.md)
- Step 4 — [`references/steps/04-corrections-loop.md`](references/steps/04-corrections-loop.md) · reads [`_shared/sync-with-main.md`](../_shared/sync-with-main.md), [`_shared/untrusted-input-boundary.md`](../_shared/untrusted-input-boundary.md), [`_shared/worktree-ignore-check.md`](../_shared/worktree-ignore-check.md)
- Step 5 — [`references/steps/05-merge.md`](references/steps/05-merge.md)
- Step 5b — [`references/steps/05b-base-run.md`](references/steps/05b-base-run.md)
- Step 5c — [`references/steps/05c-parent-note.md`](references/steps/05c-parent-note.md)
- Step 6 — [`references/steps/06-follow-ups.md`](references/steps/06-follow-ups.md) · reads [`_shared/filing-bar.md`](../_shared/filing-bar.md), [`_shared/prior-rejections.md`](../_shared/prior-rejections.md), [`_shared/untrusted-input-boundary.md`](../_shared/untrusted-input-boundary.md)
- Step 7 — [`references/steps/07-teardown.md`](references/steps/07-teardown.md)
- Step 8 — [`references/steps/08-recap.md`](references/steps/08-recap.md) · reads [`_shared/recap.md`](../_shared/recap.md)

## Notes on quality

- **The merge is the one irreversible act — gate it hard.** Everything else (follow-up issues, branch deletion) is recoverable. Merge only on green CI **and** a `CLEAN` merge state, never by overriding a failing or required check.
- **Correct, don't paper over.** Fix the red test, resolve the real conflict, address the real review note. Skipping a test, forcing past a check, or hand-stitching a snapshot to clear a conflict all *look* like progress and are worse than stopping.
- **Stay resumable.** Every step keys off live GitHub/git state, so a re-run won't double-merge, double-file, or fail because a branch is already gone.
- **Follow-ups are tracked, not narrated** — deferred work belongs in an issue (via `create-issue`), not buried in the merge report.
- **…but tracked at the root, and rationed.** The failure mode this skill is most likely to cause is not a bad merge, it's a backlog nobody can read: one merge that files a dozen leaf issues, each a symptom of one defect, is a net loss even though every issue is individually accurate. Step 6's triage is the corrective — cluster first, fold into the root second, file last.