Skill: Merge PR
Goal: Safely merge an approved Pull/Merge Request, clean up associated branches, and synchronize the local repository.
The workflow is host-agnostic — the phases are the same everywhere; only the CLI and status-field names differ. GitHub calls it a Pull Request (gh), GitLab a Merge Request (glab); "PR" below means either.
Phase 0: Detect the Host
git remote get-url origin
| Remote host | CLI | Verify with |
|---|---|---|
github.com (or GitHub Enterprise) |
gh |
gh auth status |
gitlab.com (or self-managed GitLab) |
glab |
glab auth status |
| anything else | none assumed | — |
If the host has no supported CLI, don't improvise: tell the user, and offer either a merge through the host's UI or — only if the target branch is unprotected — a plain-git merge and push.
Phase 1: Pre-Merge Verification
Verify, in the host's terms:
- The PR is open.
- It is mergeable with no conflicts.
- CI is passing — if CI exists. Absence of any pipeline or checks is not a blocker (many repos have no CI); a failing or pending one is.
- Host-specific gates are satisfied (see below).
- The source branch on the remote actually contains the expected commits. A push can silently fail to land, and an "approved" but empty PR merges nothing —
git ls-remote origin <branch>is proof; local state is not. - Confirm explicit user approval to merge.
GitHub:
gh pr view <pr-number> --json state,reviewDecision,statusCheckRollup,mergeable,mergeStateStatus,headRefName,baseRefName
state: "OPEN",mergeable: "MERGEABLE",mergeStateStatus: "CLEAN".statusCheckRollupempty = no CI configured = fine; failures = blocker.
GitLab:
glab mr view <mr-number>
glab api "projects/:fullpath/merge_requests/<mr-number>" # for the fields below
state: "opened",detailed_merge_status: "mergeable",has_conflicts: false,draft: false.blocking_discussions_resolved: true— projects often enforce resolved threads at merge; resolve them first, don't bypass.head_pipeline: null= no CI configured = fine;"failed"= blocker.
[!NOTE] Review approval is convention on both hosts unless the repo configures otherwise. On GitHub,
reviewDecisiononly gates the merge when CODEOWNERS or required reviewers exist — a blankreviewDecisionis not a blocker. On GitLab, approvals are optional on the Free tier and approval rules are Premium — treat missing approvals the same way. In both cases the user's go-ahead plus the mergeability checks above are sufficient.
Phase 2: Merge
Determine the merge strategy — do not assume squash. Strategy is repo policy, not skill policy. Resolve it in this order:
- An explicit instruction from the user or repo docs (
CONTRIBUTING.md,CLAUDE.md). - The host's own settings:
- GitHub:
gh repo view --json squashMergeAllowed,mergeCommitAllowed,rebaseMergeAllowed - GitLab:
merge_methodandsquash_optionfromglab api "projects/:fullpath"
- GitHub:
- Recent history:
git log --oneline --merges -5— a history of merge commits should not suddenly acquire squashes, and vice versa. - Only then default to squash.
GitHub:
gh pr merge <pr-number> --squash|--merge --delete-branch --subject "<commit-subject>" --body "<commit-body>"
GitLab:
glab mr merge <mr-number> [--squash] --remove-source-branch --yes
- Commit Message (when squashing): Synthesize a clean, descriptive subject using conventional commits (e.g.,
feat: PR title). If the PR description contains important details, include them in the commit body. --delete-branch(gh) /--remove-source-branch(glab) handles the remote branch cleanup on the source repository.
Phase 3: Local Cleanup and Sync
First, detect whether you are inside a linked git worktree:
if [ "$(git rev-parse --git-dir)" != "$(git rev-parse --git-common-dir)" ]; then
echo "linked worktree"
else
echo "primary checkout"
fi
If running in a worktree (Claude Code default)
git checkout and git branch -D both fail in a worktree: the default branch is checked
out in the primary worktree (two worktrees cannot share a branch), and the feature branch
is the current worktree itself. Skip both steps. Sync the primary worktree directly:
# Resolve primary worktree root — first entry in worktree list is always primary
PRIMARY=$(git worktree list --porcelain | awk '/^worktree /{sub(/^worktree /, ""); print; exit}')
git -C "$PRIMARY" fetch origin
git -C "$PRIMARY" checkout <default-branch>
git -C "$PRIMARY" pull origin <default-branch> --ff-only
The feature branch worktree is cleaned up automatically by Claude Code when the session ends — no manual deletion needed.
If running in the primary checkout
- Switch to Default Branch:
git remote show origin | grep 'HEAD branch' git checkout <default-branch> - Pull Latest Changes:
git fetch origin git pull origin <default-branch> - Sync Fork (if applicable):
If working on a fork, sync with upstream:
git remote | grep upstream && git fetch upstream && git merge upstream/<default-branch> && git push origin <default-branch> - Delete Local Branch:
git branch -D <feature-branch>
Phase 4: Post-Merge Verification
- Verify PR Closure:
Confirm the PR reports merged (
gh pr view/glab mr view) and any linked issues are also addressed —Closes #Nauto-closes on both hosts.
[!NOTE] Do not auto-invoke
/handofffrom this skill. Handoff is user-invoked only — the user decides when the session's work is complete and runs/handoffthemselves as a separate step (cadence:pr-review→merge-pr→handoff). Merging one PR does not mean the session is over.
Important Reminders
- CLI merges are worktree-safe. Both
gh pr mergeandglab mr mergeuse the host's API and do not require being in the primary checkout. The Phase 3 split above is only about the local sync steps. - Branch Deletion: Automatic source-branch deletion works when permissions allow. For PRs from forks, deletion may fail or require manual cleanup on the fork.
- Merge Conflicts: If the PR has conflicts, resolve them before attempting to merge.
- Verification: Always verify that the merge was successful and that the primary worktree is on a clean, updated default branch.