Merge PR Locally (Preserving GPG Signatures)
Merge a GitHub PR into main using a local fast-forward merge so the original
GPG-signed commit(s) land on main with their signatures intact. GitHub's merge
UI re-creates commits with GitHub's own key, losing the author's GPG signature
and showing "Unverified" on repos that enforce signing.
Read ~/.claude/skills/shared/bash-rules.md for bash command constraints.
Context
- Current branch: !
git rev-parse --abbrev-ref HEAD
- Working tree status: !
git status --short
- Open PRs: !
gh pr list --state open --json number,title,headRefName
Arguments
Optional: PR number or branch name. If omitted, auto-detect from the current
branch or find the single open PR.
Workflow
Step 1: Identify the PR
If a PR number was given as argument, use it directly.
If a branch name was given, find its PR:
gh pr list --head <branch-name> --json number,title,state --jq '.[0]'
If no argument was given, try the current branch:
git rev-parse --abbrev-ref HEAD
If not on main, look up its PR. If on main, list open PRs and pick the
single one (ask the user if there are multiple):
gh pr list --state open --json number,title,headRefName
Fetch PR details:
gh pr view <number> --json number,title,state,headRefName,baseRefName,commits,reviews,statusCheckRollup
Abort if the PR is not open or if checks are failing.
Step 2: Verify readiness
- Confirm the PR has passing checks (or no required checks).
- Show the PR title, branch, and commit count to the user.
- Verify all PR commits are GPG-signed:
git fetch origin <branch-name>
git log origin/main..origin/<branch-name> --format="%h %G? %s"
G = good signature. Warn if any commit is unsigned (N) or has a bad
signature (B).
Step 3: Stash uncommitted changes
If git status shows uncommitted changes (staged or unstaged), stash them
before switching branches:
git stash
Remember to pop the stash at the end (Step 5).
Step 4: Fast-forward merge
pr-create already rebases onto main before pushing, so the branch should
be fast-forwardable. If not, rebase first.
Ensure main is up to date:
git fetch origin main
git checkout main
git merge origin/main --ff-only
Fast-forward merge the PR branch:
git merge <branch-name> --ff-only
If --ff-only fails, the branch needs rebasing:
git checkout <branch-name>
git rebase main
git push --force-with-lease origin <branch-name>
git checkout main
git merge <branch-name> --ff-only
Push main:
git push origin main
Step 5: Clean up
Close the PR (GitHub auto-closes it if the commits match, but verify):
gh pr view <number> --json state --jq '.state'
If still open (rare edge case), close it manually:
gh pr close <number>
Delete the remote PR branch:
git push origin --delete <branch-name>
Delete the local PR branch:
git branch -d <branch-name>
Prune stale remote-tracking references (branches deleted on GitHub but
still cached locally):
git remote prune origin
Delete all other local branches that are fully merged into main:
git branch --merged main
Delete any listed branches other than main (or * main):
git branch -d <stale-branch>
If changes were stashed in Step 3, restore them:
git stash pop
Report success: show the merged commit(s) on main with signature status:
git log --oneline --format="%h %G? %s" -5
1---2name: pr-merge3description: Merge a PR locally with fast-forward to preserve GPG-signed commits, then clean up.4---56# Merge PR Locally (Preserving GPG Signatures)78Merge a GitHub PR into main using a local fast-forward merge so the original9GPG-signed commit(s) land on main with their signatures intact. GitHub's merge10UI re-creates commits with GitHub's own key, losing the author's GPG signature11and showing "Unverified" on repos that enforce signing.1213Read `~/.claude/skills/shared/bash-rules.md` for bash command constraints.1415## Context16- Current branch: !`git rev-parse --abbrev-ref HEAD`17- Working tree status: !`git status --short`18- Open PRs: !`gh pr list --state open --json number,title,headRefName`1920## Arguments2122Optional: PR number or branch name. If omitted, auto-detect from the current23branch or find the single open PR.2425## Workflow2627### Step 1: Identify the PR28291. If a PR number was given as argument, use it directly.30312. If a branch name was given, find its PR:32 ```33 gh pr list --head <branch-name> --json number,title,state --jq '.[0]'34 ```35363. If no argument was given, try the current branch:37 ```38 git rev-parse --abbrev-ref HEAD39 ```40 If not on main, look up its PR. If on main, list open PRs and pick the41 single one (ask the user if there are multiple):42 ```43 gh pr list --state open --json number,title,headRefName44 ```45464. Fetch PR details:47 ```48 gh pr view <number> --json number,title,state,headRefName,baseRefName,commits,reviews,statusCheckRollup49 ```50 Abort if the PR is not open or if checks are failing.5152### Step 2: Verify readiness53541. Confirm the PR has passing checks (or no required checks).552. Show the PR title, branch, and commit count to the user.563. Verify all PR commits are GPG-signed:57 ```58 git fetch origin <branch-name>59 git log origin/main..origin/<branch-name> --format="%h %G? %s"60 ```61 `G` = good signature. Warn if any commit is unsigned (`N`) or has a bad62 signature (`B`).6364### Step 3: Stash uncommitted changes6566If `git status` shows uncommitted changes (staged or unstaged), stash them67before switching branches:68```69git stash70```71Remember to pop the stash at the end (Step 5).7273### Step 4: Fast-forward merge7475pr-create already rebases onto main before pushing, so the branch should76be fast-forwardable. If not, rebase first.77781. Ensure main is up to date:79 ```80 git fetch origin main81 git checkout main82 git merge origin/main --ff-only83 ```84852. Fast-forward merge the PR branch:86 ```87 git merge <branch-name> --ff-only88 ```89 If `--ff-only` fails, the branch needs rebasing:90 ```91 git checkout <branch-name>92 git rebase main93 git push --force-with-lease origin <branch-name>94 git checkout main95 git merge <branch-name> --ff-only96 ```97983. Push main:99 ```100 git push origin main101 ```102103### Step 5: Clean up1041051. Close the PR (GitHub auto-closes it if the commits match, but verify):106 ```107 gh pr view <number> --json state --jq '.state'108 ```109 If still open (rare edge case), close it manually:110 ```111 gh pr close <number>112 ```1131142. Delete the remote PR branch:115 ```116 git push origin --delete <branch-name>117 ```1181193. Delete the local PR branch:120 ```121 git branch -d <branch-name>122 ```1231244. Prune stale remote-tracking references (branches deleted on GitHub but125 still cached locally):126 ```127 git remote prune origin128 ```1291305. Delete all other local branches that are fully merged into main:131 ```132 git branch --merged main133 ```134 Delete any listed branches other than `main` (or `* main`):135 ```136 git branch -d <stale-branch>137 ```1381396. If changes were stashed in Step 3, restore them:140 ```141 git stash pop142 ```1431447. Report success: show the merged commit(s) on main with signature status:145 ```146 git log --oneline --format="%h %G? %s" -5147 ```