Per-operation playbooks for gh pr workflows. Every write op with author voice (--body / --title / merge --subject / --body) is preview-gated per hard rule 8 — quote the draft verbatim in chat, name the send command, wait for explicit user approval, then send.
# Single PR with metadata
gh pr view <n> --repo <o>/<r>
# PR + comment thread (the discussion is half the story)
gh pr view <n> --repo <o>/<r> --comments
# Diff (use this — not local git diff — to see exactly what the PR proposes)
gh pr diff <n> --repo <o>/<r>
# CI checks (snapshot)
gh pr checks <n> --repo <o>/<r>
# CI checks (block until all complete — for use during merge gate)
gh pr checks <n> --repo <o>/<r> --watch --interval 10
# List with filters
gh pr list --repo <o>/<r> --state open --base main --limit 20
# Inline review comments on specific lines (default --comments shows top-level only)
gh api repos/<o>/<r>/pulls/<n>/comments --jq '.[] | {path,line,user:.user.login,body}'
# Detailed merge-readiness fields
gh pr view <n> --repo <o>/<r> --json mergeable,mergeStateStatus,reviewDecision,statusCheckRollup
PR title shows in lists, notifications, and (for squash merges) becomes the commit message subject on main. Body should explain why + how to verify. Always preview both.
Drafted PR for
<o>/<r>:Title:
<title>Base: main ← Head:
<branch>Body:
> <body> > ``` > > Command: `gh pr create --base main --head <branch> --title "..." --body "..."` > > Send? ### Send ```bash gh pr create --repo <o>/<r> \ --base main \ --head <branch> \ --title "<approved title>" \ --body "$(cat <<'EOF' <approved body> EOF )"
## Summary
<1-3 bullets — what changed and why>
## Test plan
- [x] <thing that was tested>
- [ ] <thing the reviewer should test>
Closes #<n> <!-- if this PR resolves an issue, link it so merge auto-closes -->
The "Closes #N" / "Fixes #N" footer is load-bearing — it triggers GitHub's auto-close on merge. Without it the issue stays open after the PR lands and someone has to clean up.
For work-in-progress or pre-review polish, create as draft:
gh pr create … --draft
Promote later (mechanical, no preview): gh pr ready <n>.
Same discipline as issue comments. Top-level PR comments are different from inline review comments (next section).
gh pr comment <n> --repo <o>/<r> --body "$(cat <<'EOF'
<approved body>
EOF
)"
Three flavours: --approve, --request-changes, --comment (review-level, not inline). All three may include a body; if they do, preview the body.
# Approve with no message — preview not required (the action itself is the signal)
gh pr review <n> --repo <o>/<r> --approve
# Approve with a parting comment — preview the body
gh pr review <n> --repo <o>/<r> --approve --body "<approved body>"
# Request changes — body is required and definitely preview-gated
gh pr review <n> --repo <o>/<r> --request-changes --body "<approved body>"
# Comment-only review (no approve/block) — preview the body
gh pr review <n> --repo <o>/<r> --comment --body "<approved body>"
Inline-line comments (gh api repos/<o>/<r>/pulls/<n>/comments) are heavier — currently out of scope; use the GitHub UI or extend this reference when first needed.
gh pr edit <n> --repo <o>/<r> \
--title "<new title>" \
--body "$(cat <<'EOF'
<new body>
EOF
)"
Mechanical edits (labels, reviewers, milestone, base branch) don't need preview:
gh pr edit <n> --repo <o>/<r> \
--add-label "ready-for-review" \
--remove-label "wip" \
--add-reviewer <user> \
--milestone "v2.11.0"
Never invoke gh pr merge without running this gate first. This is the discipline distilled from PR #11.
# 1. Merge-readiness JSON
gh pr view <n> --repo <o>/<r> --json mergeable,mergeStateStatus,reviewDecision \
--jq '{mergeable,mergeStateStatus,reviewDecision}'
# Need: mergeable=MERGEABLE, mergeStateStatus=CLEAN (or UNSTABLE if non-required checks fail)
# 2. All checks pass (use --watch to block during a CI run)
gh pr checks <n> --repo <o>/<r>
# 3. Review the actual diff one more time
gh pr diff <n> --repo <o>/<r> | less
# 4. Confirm the PR body still accurately describes the diff
# (PRs that grew during review often have stale descriptions — edit first if so)
gh pr view <n> --repo <o>/<r> --json title,body --jq '.body' | head -50
Surface the result in chat as a green/red checklist before proposing the merge command. If anything is red, stop and report — don't merge through a failing gate.
| Situation | Strategy | Why |
|---|---|---|
| Fix or feature branch with multiple WIP commits all serving one logical change | Squash (--squash) |
Clean single-line history on main; the PR is the unit, individual commits were drafts |
Each commit on the branch is independently meaningful and worth preserving in git log |
Merge commit (--merge) |
Preserves authorship and intermediate steps; useful for collaborative branches |
Repo enforces linear history (gh api repos/<o>/<r> --jq .allow_rebase_merge) and commits are individually clean |
Rebase (--rebase) |
Each commit lands on main as a separate commit, no merge bubble |
| Mixed / unclear | Ask the user | Don't guess; the project's history style is theirs to decide |
Default for one-off PRs in this project family (claude-mods, etc.): --squash.
Check repo allowance once via gh api repos/<o>/<r> --jq '{allow_squash_merge,allow_merge_commit,allow_rebase_merge}'. If only some are enabled, that constrains the choice.
When using --squash, the merge commit's subject and body land on main as a public commit. If passing --subject / --body, preview both (hard rule 8):
gh pr merge <n> --repo <o>/<r> --squash \
--subject "<approved subject — typically: PR title (#<n>)>" \
--body "$(cat <<'EOF'
<approved body — typically: one-paragraph summary of the change>
EOF
)"
Without --subject / --body, gh uses the PR title and the concatenated commit messages — no preview required, but state in chat which you're using.
After the pre-merge gate is green and the strategy is chosen:
Pre-merge gate green:
- mergeable: MERGEABLE / mergeStateStatus: CLEAN
- All 3 checks pass (validate 18s, Socket x2)
- Diff reviewed, PR body matches
Proposing:
gh pr merge <n> --repo <o>/<r> --squash --subject "<…>" --body "<…>"Branch deletion: not bundled — separate step after merge if you want it.
Merge?
Then on approval:
gh pr merge <n> --repo <o>/<r> --squash --subject "..." --body "..."
gh pr merge --delete-branch exists but couples merge + delete. Keep them separate. Delete branches as a discrete operation after merge, with its own confirmation — both because branch deletion is destructive and because a currently-checked-out branch can't be deleted at all (relevant if the operating worktree is on the PR branch).
# After merge + user OK:
git -C <local-repo> fetch origin --prune
git -C <local-repo> checkout --detach origin/main # leave the branch if it's checked out
git -C <local-repo> branch -D <branch> # local
git push origin --delete <branch> # remote
# Plain close — no preview
gh pr close <n> --repo <o>/<r>
# Close with a parting comment — preview the comment first
gh pr comment <n> --repo <o>/<r> --body "..." # preview-gated
gh pr close <n> --repo <o>/<r>
Superseded:
Closing in favour of #, which takes a different approach: . Thanks for the work here — pieces of the idea live on in the new PR.
Won't merge:
Closing — after discussion, decided not to land this because . Detailed why in the thread above. Not a rejection of the code quality, just the direction.
Common workflows
Standard fix flow
1. Branch off origin/main 2. Commit; push branch 3. Draft PR body in chat, preview with user, gh pr create (preview-gated) 4. CI runs; gh pr checks --watch 5. Address review feedback if any (responses preview-gated) 6. Pre-merge gate 7. Merge (squash by default; explicit user OK) 8. Branch cleanup (separate step, explicit OK)Reviewing a PR
1. gh pr view <n> --comments # full context including prior discussion 2. gh pr diff <n> # what actually changed 3. gh pr checks <n> # CI status 4. Read the linked issue(s) for original intent 5. Draft review body in chat → preview → gh pr review (preview-gated)Hotfix with auto-close
1. Branch off main, fix, commit, push 2. PR body includes "Fixes #<bug-issue>" (auto-closes on merge) 3. Standard merge flow 4. After merge: post a closing-credit comment on #<bug-issue> with version + PR linkAnti-patterns
- ❌ Merging without running the pre-merge gate (
mergeStateStatus/ checks / diff).- ❌ Bundling
--delete-branchinto the merge command — couples two destructive decisions into one.- ❌ Defaulting to
--merge(merge-commit) when squash gives a cleaner history; only use it when individual commits matter.- ❌ Sending a review body, PR description, or comment without showing the draft to the user first (hard rule 8).
- ❌ Promising follow-up work in a PR description and not capturing it as an issue.
- ❌ Skipping the "Closes #N" footer in PR body — leaves issues unclosed after merge.
- ❌ Treating
mergeable: UNKNOWNas green — wait for GitHub to compute (re-poll).