The pull-request workflow, from draft to drained stack
- Scope a PR to one reviewable purpose and write a commit message and description that say why — ≤50-character imperative subject, blank line, body;
Fixes #Ncloses issues only on the default branch - Run the review loop — draft vs ready, Comment / Approve / Request changes, pending review comments — and say what actually blocks a merge
- Read required status checks as gates: a job skipped by its condition counts as success, while a workflow that never runs blocks with "Waiting for status to be reported"
- Choose merge commit, squash or rebase-and-merge knowing what each does to
main's history and to branches built on the PR - Drain a hand-built stack bottom-up: retarget the child before its base branch is deleted, then
rebase --ontoand force-push with a lease
Read
A pull request is a request for someone's attention
FlagA pull request asks a reviewer to understand a change well enough to share responsibility for it. Everything about a good PR makes that cheaper.
One purpose per PR
GitHub's own advice: "Small, focused pull requests are easier to review and safer to merge." The test is whether you can state the purpose in one sentence. "Bump the ORM, refactor the repository layer, and add order export" is three PRs:
PR 1 Bump the ORM to 7.2 (mechanical; reviewer skims)
PR 2 Move order queries into OrderRepository (refactor; no behaviour change)
PR 3 Add CSV export for orders (the feature; the real review)
Each is easy to say yes to, easy to revert alone, and fails CI for one reason. If PR 3 needs PR 2, that's a stack — the complex example covers merging one.
Before asking anyone else, review it yourself: read the whole diff in the Files changed tab, look for debug output, stray files and unrelated formatting, and check that CI is green.
Commit messages that survive
Git's own manual: "begin the commit message with a single short (no more than 50 characters) line summarizing the change, followed by a blank line and then a more thorough description." Pro Git adds: write it in the imperative — "Fix bug", not "Fixed bug" — and use the body for the motivation.
Round cart totals once, after summing
Line totals were rounded individually and then summed, so a cart of
three $0.333 items showed $0.99 instead of $1.00. Sum the exact
amounts and round the result.
The subject is what git log --oneline, git revert and every review tool show. The body is for
the person running git blame on this line in two years: why, not a list of files.
A description that does the reviewer's first five minutes
- Why — the problem, or a link to the issue.
- What — the approach, in a few lines.
- Where to look — "the logic is in
pricing.ts; the rest is fixture updates". - How it was checked — tests added, what you ran by hand.
Fixes #88 (or Closes, Resolves) in the description links the issue and closes it when the PR
merges — but only when the PR targets the repository's default branch. Against any other base,
GitHub's docs say the keywords are ignored.
What actually gates a merge
FlagA green "Merge" button means the repository's rules are satisfied — not that the change is right. Those rules live in branch protection (or the newer rulesets) on the target branch. The settings you'll meet most, as GitHub's docs describe them:
| Setting | Effect |
|---|---|
| Require pull request reviews | N approvals from people with write access; a write-access "Request changes" blocks until that reviewer approves |
| — dismiss stale approvals | a push that changes the diff withdraws existing approvals |
| — require review from code owners | files with a CODEOWNERS entry need their owner's approval |
| Require status checks | named checks must pass on the PR's latest commit |
| — require branches to be up to date ("strict") | the PR must also contain the base branch's latest commit |
| Require conversation resolution | every review thread resolved |
| Require linear history | only squash or rebase merges |
By default a protection rule also blocks force-pushes and deletion of that branch — which is
why git push --force to a protected main fails no matter what you type. And by default the
rules don't apply to repository admins unless "Do not allow bypassing the above settings" is on.
Status checks: the CI gate
A status check is a pass/fail/pending result attached to a commit, usually posted by CI. Only checks marked required gate the merge; the rest are information.
% gh pr checks 42 --required --watch # wait for the required checks on PR #42
Strict vs loose. With "require branches to be up to date", a PR that was green yesterday must
take in the latest main (Update branch, or rebase and push) and pass again before it can merge.
That catches two PRs that each pass alone and break together, at the cost of more CI runs. A merge
queue gives the same guarantee without making every author update by hand.
Auto-merge: say yes now, merge when it's ready
gh pr merge 42 --squash --auto
When reviews or checks are still pending, --auto enables auto-merge: GitHub merges as soon as
every requirement is met. GitHub switches it off again if someone without write access pushes to
the branch or changes its base branch.
Why this matters to you as an author
- A PR that's "approved" but red can't merge; a PR that's green but unreviewed can't either. Read the merge box — it lists exactly which requirement is missing.
- A required check is only as good as the job behind it. The complex example shows two ways a required check reports "passing" or "blocked" for reasons unrelated to your code.
Draining a stack of pull requests, bottom-up
FlagA hand-built stack: PR #1 merges feature-a into main; PR #2 merges feature-b into
feature-a, so it shows only b's diff. Reviews happen in parallel. Merging is where stacks go
wrong, and there are two separate failure modes.
Failure 1: the base branch disappears and takes PR #2 with it
GitHub's docs describe the happy path: "If you delete a head branch after its pull request has been merged, GitHub checks for any open pull requests in the same repository that specify the deleted branch as their base branch" and changes their base to the merged PR's base. The docs don't say whether every way of deleting the branch triggers that.
Not every way does. gh pr merge 1 --delete-branch deletes the remote branch through the API, and
the GitHub CLI issue tracker has an open bug from 2020 (cli/cli#1168) in which the child PR is
closed instead of retargeted; a commenter there traced it to the API call that deletes the
branch. A similar report from August 2026 (cli/cli#14223) is labelled a platform bug and still
open. One commenter on #1168 found that turning on the repo's "Automatically delete head
branches" setting avoided it in 2020 and no longer did by 2024. Once closed, the PR can't be
retargeted (Cannot change the base branch of a closed pull request.), and in #14223 reopening it
was refused too — so PR #2 may have to be recreated, losing its review history.
Don't depend on which deletion path you took. Retarget first, then merge and delete:
gh pr edit 2 --base main # PR #2 now targets main (its diff briefly includes a's commits)
gh pr merge 1 --squash # merge the bottom PR; if gh offers to delete the branch, say no
Failure 2: after a squash, the upper branch still carries the old commits
main now has one squash commit; feature-b still has A1, A2 underneath B1. Rebase only
b's own commits onto main — your local feature-a branch marks where they start, which is why
it's worth keeping until this step is done:
git fetch
git switch feature-b
git rebase --onto origin/main feature-a # replay feature-a..feature-b
git push --force-with-lease # PR #2 now shows only b's diff again
git push origin --delete feature-a # safe now: nothing targets it
With a merge commit instead of a squash, a plain git rebase origin/main does the same job,
because A1, A2 are already in main's history. With a one-commit feature-a, rebase skips the
duplicate on its own.
The rule
Drain bottom-up, one PR at a time: retarget the next PR to main → merge the bottom → rebase the
next branch onto main (with --onto after a squash) → force-push with a lease → delete the old
base. Merging from the top down doesn't work: merging PR #2 first lands b in feature-a, not in
main.
Quiz
Your branch bumps the ORM, refactors the order queries into a repository class, and adds CSV export (1,800 lines). How should it reach review?
FlagWhich commit message follows the git manual's and Pro Git's guidance?
FlagA PR into release-2.4 (not the default branch) says Fixes #88 and has merged. Issue #88 is still open. Why?
A reviewer chose Request changes, yet the author merged five minutes later without approval. How?
FlagYou left twelve line comments on a teammate's PR an hour ago. They say they can't see any. What happened?
FlagA docs-only PR is blocked: the required check build says "Waiting for status to be reported." The build workflow only runs when files under src/ change. Why is it blocked?
The required check test is green on a PR — but its job has a condition that was false for this PR, so it never ran. Can the PR merge?
PR #10 was squash-merged. You kept committing on the same branch and opened PR #11 from it. PR #11 lists PR #10's commits again. Why?
FlagPR #2 (feature-b → feature-a) is stacked on PR #1 (feature-a → main). Both are approved. What's the safe order?
Fill in the blank
Open the PR as a draft, then later mark it ready for review.
Flaggh pr create --fill gh pr
In a PR to the default branch, make issue 88 close automatically when the PR merges.
Flag #88Before merging the bottom PR of a stack, point the PR above it at main.
gh pr edit 2 main
Squash-merge PR 42 automatically as soon as its required reviews and checks pass.
Flaggh pr merge 42 --squash PR 1 (feature-a) was squash-merged. Replay only feature-b's own commits onto main, then update its PR safely.
git rebase --onto origin/main feature-b git push
Further reading
- GitHub Docs: Helping others review your changes
- GitHub Docs: Pull requests (drafts)
- GitHub Docs: Reviewing proposed changes in a pull request
- GitHub Docs: About protected branches
- GitHub Docs: Troubleshooting required status checks
- GitHub Docs: Using conditions to control job execution (a skipped job reports Success)
- GitHub Docs: About merge methods on GitHub
- GitHub Docs: Merging a pull request
- GitHub Docs: Linking a pull request to an issue
- GitHub Docs: Automatically merging a pull request
- GitHub Docs: About stacked pull requests
- GitHub Changelog: Stacked pull requests are now in public preview (2026-07-30)
- GitHub CLI manual: gh pr merge
- Git docs: git-commit (Discussion: commit messages)
- Pro Git: Contributing to a Project (commit guidelines)