pr-ops.md 9.9 KB

Pull Request Operations Reference

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.

Reads (no preview)

# 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

Create (preview required — title AND body)

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.

Chat preview

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 / \ --base main \ --head \ --title "" \ --body "$(cat <<'EOF' EOF )"


### Body shape (claude-mods convention)

markdown

Summary

<1-3 bullets — what changed and why>

Test plan

  • 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.
    
    ### Draft vs ready
    
    For work-in-progress or pre-review polish, create as draft:
    
    

    bash gh pr create … --draft

    
    Promote later (mechanical, no preview): `gh pr ready <n>`.
    
    ## Comment (preview required)
    
    Same discipline as issue comments. Top-level PR comments are different from inline review comments (next section).
    
    ### Send
    
    

    bash gh pr comment --repo / --body "$(cat <<'EOF' EOF )"

    
    ## Review (preview required for body)
    
    Three flavours: `--approve`, `--request-changes`, `--comment` (review-level, not inline). All three may include a body; if they do, **preview the body**.
    
    

    bash

    Approve with no message — preview not required (the action itself is the signal)

    gh pr review --repo / --approve

    Approve with a parting comment — preview the body

    gh pr review --repo / --approve --body ""

    Request changes — body is required and definitely preview-gated

    gh pr review --repo / --request-changes --body ""

    Comment-only review (no approve/block) — preview the body

    gh pr review --repo / --comment --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.
    
    ## Edit title / body (preview required)
    
    

    bash gh pr edit --repo / \ --title "" \ --body "$(cat <<'EOF' EOF )"

    
    Mechanical edits (labels, reviewers, milestone, base branch) don't need preview:
    
    

    bash gh pr edit --repo / \ --add-label "ready-for-review" \ --remove-label "wip" \ --add-reviewer \ --milestone "v2.11.0"

    
    ## Pre-merge gate
    
    **Never invoke `gh pr merge` without running this gate first.** This is the discipline distilled from PR #11.
    
    

    bash

    1. Merge-readiness JSON

    gh pr view --repo / --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 --repo /

    3. Review the actual diff one more time

    gh pr diff --repo / | 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 --repo / --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.
    
    ## Merge strategy decision tree
    
    | 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.
    
    ### Squash with custom subject/body
    
    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):
    
    

    bash gh pr merge --repo / --squash \ --subject ")>" \ --body "$(cat <<'EOF' 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.
    
    ## Merge — the final call
    
    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:
    
    

    bash gh pr merge --repo / --squash --subject "..." --body "..."

    
    ## Branch deletion (separate explicit step)
    
    `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).
    
    

    bash

    After merge + user OK:

    git -C fetch origin --prune git -C checkout --detach origin/main # leave the branch if it's checked out git -C branch -D # local git push origin --delete # remote

    
    ## Close (without merging)
    
    

    bash

    Plain close — no preview

    gh pr close --repo /

    Close with a parting comment — preview the comment first

    gh pr comment --repo / --body "..." # preview-gated gh pr close --repo /

    
    ### Closing-comment templates
    
    **Superseded:**
    > Closing in favour of #<m>, which takes a different approach: <one sentence>. 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 <reason>. 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
      
      
    9. gh pr view --comments # full context including prior discussion

    10. gh pr diff # what actually changed

    11. gh pr checks # CI status

    12. Read the linked issue(s) for original intent

    13. Draft review body in chat → preview → gh pr review (preview-gated)

      
      ### Hotfix with auto-close
      
      
    14. Branch off main, fix, commit, push

    15. PR body includes "Fixes #" (auto-closes on merge)

    16. Standard merge flow

    17. After merge: post a closing-credit comment on # with version + PR link ```

      Anti-patterns

      • ❌ Merging without running the pre-merge gate (mergeStateStatus / checks / diff).
      • ❌ Bundling --delete-branch into 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: UNKNOWN as green — wait for GitHub to compute (re-poll).