Skip to content
Back to skills

Merge Pr

ASecurity

Merge a PR into its base branch. Defaults to a merge commit; pass --squash or -s to squash instead. Falls back to squash when the repo disallows merge commits. Single PR by number or current branch. Refuses to squash/rebase into main on a git-flow repo — use /promote-release there instead.

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
ai-agentsshellbashgit

Works with

  • cli

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add dryvist/claude-code-plugins --skill merge-pr --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Merge Pr?

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

Security grade badge for Merge Pr
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/dryvist-merge-pr/badge)](https://www.skillsdirectory.com/skills/dryvist-merge-pr)

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: merge-pr
description: >-
  Merge a PR into its base branch. Defaults to a merge commit; pass --squash
  or -s to squash instead. Falls back to squash when the repo disallows merge
  commits. Single PR by number or current branch. Refuses to squash/rebase into
  main on a git-flow repo — use /promote-release there instead.
metadata:
  argument-hint: "[PR_NUMBER] [--squash|-s]"
---

# Merge PR

Validates readiness, invokes `/finalize-pr` for soft blocks, then merges.
Hard stops abort immediately. Some cases require human action: closed/merged PR,
draft, unresolvable conflicts, unrecoverable CI, or more than 100 review threads.

> **State warning**: Branch state, remote tracking, and PR status change between
> invocations. Re-run all git/gh commands from Step 1.

## Critical Rules

- Run the GraphQL readiness gate on every invocation before merging
- PR metadata updates are `/finalize-pr`'s responsibility
- Invoke `/finalize-pr` for soft blocks; abort on hard stops with reason
- Merge commit is the default; `--squash`/`-s` opts into a squash merge
- Squash and rebase are never used to merge into `main` on a git-flow repo —
  Step 0 guards this. Plain merge commit into `main` on a git-flow repo IS
  allowed — that's the `/promote-release` path.
- If the PR belongs to a stack, merging it lands every unmerged layer below it
  atomically, and a partial merge auto-retargets the layers above. Do not treat
  those as orphans; see `/pr-stacks`.

## Step 0: Refuse Squash/Rebase Into Main On Git-Flow Repos

Resolve the PR's base branch and the repo's default branch:

```bash
BASE_BRANCH=$(gh pr view <PR_NUMBER> --json baseRefName --jq '.baseRefName')
DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name')
```

If `BASE_BRANCH == main` AND `DEFAULT_BRANCH == develop` (repo is on git-flow —
see /gh-cli-patterns Canonical Default-Branch Detection):

- **`--squash`/`-s` was passed → abort immediately, no finalize**:

  > "This is a `develop` → `main` promotion PR on a git-flow repo. `main` accepts
  > merge commits only — squash and rebase are banned by ruleset, no exceptions.
  > Run `/promote-release` instead, or merge manually with `gh pr merge --merge`."

- **No `--squash` (default merge commit) → this is a legitimate promotion.**
  Continue to Step 1, but note in the final report that `/promote-release` is
  the normal entry point for this (it also opens the PR and explains
  release-please) — this invocation may be a manual equivalent of that.

Otherwise (trunk repo, or a git-flow repo's ordinary feature PR into `develop`),
continue to Step 1 with the requested (or default) merge method.

## Step 0.5: Resolve The Merge Method

```bash
gh repo view --json mergeCommitAllowed,squashMergeAllowed,rebaseMergeAllowed
```

Decide before attempting any merge — never retry after a failed `gh pr merge`:

1. `--squash`/`-s` was passed → use squash. If `squashMergeAllowed == false`,
   abort: "Repo does not allow squash merges."
2. Otherwise → use a merge commit. If `mergeCommitAllowed == false` and
   `squashMergeAllowed == true`, fall back to squash and say so in the final
   report ("repo disallows merge commits — squashed instead").
3. Neither `mergeCommitAllowed` nor `squashMergeAllowed` (and no explicit
   `--squash`) → abort, reporting what the repo does allow.

Carry the resolved method (`merge` or `squash`) into Step 2 and Step 3.

## Step 1: Validate PR Ready

Run the **canonical PR-readiness gate** and **canonical code-scanning alert count**
from /gh-cli-patterns. Replace `<OWNER>`, `<REPO>`, `<PR_NUMBER>` per the
placeholder convention. CodeQL is separate from CI — check both.

### 1.1 Hard stops (abort immediately, no finalize)

| Condition | Message |
|-----------|---------|
| `state != OPEN` | "PR is closed or merged — nothing to do" |
| `isDraft == true` | "PR is a draft — mark it ready for review first" |
| `human:review` label present | "PR is gated on human review before merge (see pr-standards Human-Review Gate). Merge only on an explicit same-session user instruction to merge THIS PR; when so instructed, `gh pr edit <PR_NUMBER> --remove-label human:review` first, then proceed. Otherwise abort." |
| `reviewThreads.pageInfo.hasNextPage == true` | ">100 review threads — paginate manually and re-verify before merging" |

### 1.2 Soft blocks (invoke /finalize-pr, then re-verify)

If any of the following fail, proceed to Step 1.3 (auto-finalize), then re-run the
full gate. If the gate still fails after finalization, abort with the specific reason.

| Check | Must be | Abort message (if still failing after finalize) |
|-------|---------|------------------------------------------------|
| `mergeable` | `MERGEABLE` | "PR has git conflicts — unresolvable" |
| `mergeStateStatus` | `CLEAN` or `HAS_HOOKS` | "PR merge state is {value} — still blocked after finalize" |
| `reviewDecision` | `APPROVED` or `null` | "Review decision is {value}" |
| `statusCheckRollup.state` | `SUCCESS` | "CI is {state} — unrecoverable" |
| All `reviewThreads.isResolved` | `true` | "Unresolved review threads remain" |
| CodeQL alert count | `0` | "Open CodeQL alerts remain — run /resolve-codeql manually" |

### 1.3 Auto-finalize

Invoke `/finalize-pr <PR_NUMBER>`. If it reports human intervention needed, abort with
its reason. Then re-run the full gate (Steps 1.1 + 1.2); if any soft block persists,
abort with the specific failing field.

## Step 2: Generate Commit Message (squash only)

Merge commits use GitHub's default subject — skip this step entirely when
Step 0.5 resolved to `merge`.

For a squash, analyze the full changeset to generate a release-note-friendly
commit message. Replace `<PR_NUMBER>` and `<BASE_BRANCH>` (from Step 0) before
running:

```bash
git fetch origin <BASE_BRANCH>
git diff origin/<BASE_BRANCH>...HEAD
git log --oneline origin/<BASE_BRANCH>..HEAD
```

Generate:

- **Title**: Conventional commit format (`<type>: <description>`, under 70 chars)
- **Body**: 2-3 line explanation of what changed and why

Types: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`

Store the title in a shell variable:

```bash
SQUASH_TITLE="<generated title>"
```

## Step 3: Execute The Merge

Capture the branch name before merging (needed for cleanup). Replace `<PR_NUMBER>` before running:

```bash
BRANCH=$(gh pr view <PR_NUMBER> --json headRefName --jq '.headRefName')
```

Merge without `--delete-branch` (avoids `git switch` failure in bare+worktree repos).
Use the heredoc body pattern from /gh-cli-patterns.

**Squash** (Step 0.5 resolved to squash, whether by `--squash`/`-s` or fallback):

```bash
gh pr merge <PR_NUMBER> --squash --subject "$SQUASH_TITLE" --body "$(cat <<'EOF'
... generated body ...
EOF
)"
```

**Merge commit** (default):

```bash
gh pr merge <PR_NUMBER> --merge
```

Single-quoted `'EOF'` prevents shell expansion. Closing `EOF` must be alone on its own line with no leading whitespace.

Delete the remote branch (GitHub may have auto-deleted it on merge — `|| true` handles that):

```bash
git push origin --delete "$BRANCH" || true
```

Find and remove the local worktree by branch name (works in any repo layout):

```bash
WORKTREE_PATH=$(git worktree list --porcelain | awk -v b="refs/heads/$BRANCH" '/^worktree/{p=$2} $0=="branch "b{print p}')
[ -n "$WORKTREE_PATH" ] && git worktree remove "$WORKTREE_PATH" || true
```

Delete the local branch ref (safe no-op if absent):

```bash
git branch -d "$BRANCH" || true
```

## Step 4: Sync Base Branch

```bash
git switch <BASE_BRANCH>
git pull origin <BASE_BRANCH>
git worktree prune
```

## Integration

Invoke at any time — auto-finalizes if needed:

```text
/merge-pr             # Current branch PR, merge commit
/merge-pr 42          # Specific PR number, merge commit
/merge-pr 42 --squash # Specific PR number, squash merge
/merge-pr -s          # Current branch PR, squash merge
```

## Related Skills

- finalize-pr (github-workflows) — invoked automatically by merge-pr when blockers are found
- rebase-pr (github-workflows) — alternative merge strategy that preserves commit history
- pr-stacks (github-workflows) — stack-aware merge: landing a layer takes every unmerged layer below it
- promote-release (github-workflows) — the develop → main promotion path; calls this skill directly and never squashes
- pr-standards (github-workflows) — PR authoring and review standards
- gh-cli-patterns (github-workflows) — canonical gh CLI command shapes, placeholder convention, PR-readiness gate, default-branch detection

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…