A pull request description is the only part of a change written for the reviewer rather than for the machine. It is also, in most repositories, either empty or a restatement of the branch name.
AI is genuinely good at fixing this, with one condition that determines whether the result helps or adds noise: a summary that repeats what the diff already shows is worse than no summary, because it costs reading time and delivers nothing.
The short answer
Section titled “The short answer”Generate from the branch diff, not from the commit list, and ask for the things a reviewer cannot see.
git diff main...HEADWhat it doesShows every change on your branch relative to where it diverged from main.
Why we run itThis is what the reviewer will read. Commit messages describe the journey; this is the destination.
Expected resultA unified diff of the branch's net effect.
Then ask for: what changed and why, what a reviewer should look at first, what is risky, and what would need to happen if it went wrong.
Commits or the final diff?
Section titled “Commits or the final diff?”Both are available and they answer different questions.
The commit list is the journey. It shows the order work happened in, including the false start you reverted in commit four.
The branch diff is the destination. It is what merges, and it is what GitHub shows a reviewer by default.
Use the diff as the primary input. Use the commits as supporting context where the history is meaningful — a branch with deliberate, self-contained commits tells a story worth summarising, and a branch with “wip”, “fix”, “fix again” does not.
{/* Supporting context: what the commits claim happened */}git log --oneline main...HEADThe mismatch worth watching for: a branch whose commits describe three features and whose diff shows one. That usually means work was reverted, and a summary generated from commits alone will describe changes that are not in the pull request.
What a good description contains
Section titled “What a good description contains”Reviewers need four things, and only the first is visible in the diff.
What changed. One or two sentences. The diff shows the detail; this is the orientation.
Why. The reason the change exists. Not inferable — you supply it.
What to look at first. The single most useful line in any pull request description. “The retry
logic in client.py is the substantive part; the rest is call-site updates” saves a reviewer ten
minutes of working out where to concentrate.
What is risky, and what happens if it is wrong. Behaviour changes, migrations, anything touching data or a public interface. This is where a reviewer’s attention should go and where they most often have to guess.
A structure that works, and that AI fills in reliably once you give it the diff and one sentence of intent:
## What
Adds retry-with-backoff to the payment client for transient upstream failures.
## Why
Roughly 2% of checkout failures were single transient 503s from the provider thatwould have succeeded on a retry. Closes #482.
## Where to look
`src/payments/client.py` is the substantive change. The other files are call-siteupdates and one new test fixture.
## Risk
Retries change timing on the checkout path — worst case is a slower failure ratherthan a faster one. Retry count and backoff are configurable and default to 3/exponential.No schema or API changes.
## Testing
New unit tests cover the retry path and the exhaustion case. Verified manually againstthe provider sandbox.Note what is generated and what is not. The What, Where to look and Testing sections come from the diff. The Why, the issue number and the risk assessment come from you — and are the sections a reviewer reads most carefully.
The prompt that works
Section titled “The prompt that works”The pattern, which generalises well:
Here is the diff of my branch against main. The purpose of this change is [one sentence].
Write a pull request description covering: what changed, where a reviewer should focus, what is risky, and what testing exists. Do not restate the diff file by file. If something in the diff looks unrelated to the stated purpose, say so.
The last sentence is the underrated one. It turns description generation into a check: a change that does not fit the stated purpose is either something you forgot you did, or something that belongs in a different pull request.
Architecture, tests and rollout
Section titled “Architecture, tests and rollout”Three categories worth asking about explicitly, because they are what a description usually omits.
Architectural changes. A new dependency between modules, a new external call, a changed interface, a new dependency in the manifest. Reviewers care about these disproportionately, and they are easy to miss in a large diff.
Does this diff introduce any new dependencies between modules, or any new external calls?
Test changes. What was added, what was modified, and — the important one — what was removed or weakened. A deleted assertion or a loosened timeout is a change to your safety net and belongs in the description, not discovered in review.
Which tests changed? Were any assertions removed or weakened?
Rollout considerations. Migrations, feature flags, configuration that must change, ordering requirements between this and another deploy. A model can spot the presence of a migration file; it cannot know your deployment process, so treat this as a prompt rather than an answer.
Issue linkage
Section titled “Issue linkage”Linkage is deterministic and worth not delegating. GitHub closes an issue when a merged pull request
body contains a closing keyword — Closes #482, Fixes #482, Resolves #482.
Two things go wrong when this is generated:
An invented issue number. A model asked to reference an issue, without being told which, may produce a plausible one. That links your change to somebody else’s unrelated work.
A missing or wrong keyword. Related to #482 does not close anything. If your process expects the
issue to close on merge, the keyword matters.
Supply the number yourself. It is four characters and the failure mode is somebody else’s issue closing silently.
Draft pull requests and iterative summaries
Section titled “Draft pull requests and iterative summaries”A description written when a branch is half-finished is wrong by the time it merges, which is an argument for generating it late rather than early.
The workflow that fits draft pull requests:
Open as a draft with a placeholder. One line saying what you are attempting. Nobody is reviewing yet, so the description costs nothing.
Regenerate before marking ready. At that point the diff is final and the summary describes what will actually merge.
Regenerate again after substantial review changes. A branch that changed significantly during review has a description describing its earlier self — and the reviewer coming to it fresh for a second look reads that description first.
The last one is the step nobody does, and it matters most on long-running pull requests where the final state bears little resemblance to the original. A stale description is worse than none, because it is confidently wrong about what is being merged.
Where your team squash-merges, there is a second reason to keep it current: the pull request description frequently becomes the squashed commit message, which means it is what history analysis will read in a year.
What a summary cannot do
Section titled “What a summary cannot do”It cannot supply the reason. Repeated because it is the most common gap.
It cannot assess business risk. Whether this change is acceptable to ship on a Friday is a judgement about your organisation.
It cannot know your deployment. Whether a migration needs to run before or after the deploy is knowledge about your process.
It cannot make a bad pull request good. A branch touching forty files across three concerns has no good description. The fix is to split it — and a description that reads as three unrelated sections is telling you exactly that.
Sizing the description to the change
Section titled “Sizing the description to the change”A three-line change does not need five headings, and applying a template uniformly is how descriptions become boilerplate that reviewers learn to skip.
| Change | Description |
|---|---|
| Typo, dependency bump, formatting | One line. Sometimes the title is enough |
| Small, self-contained fix | What and why, two sentences |
| Feature or behaviour change | The full structure above |
| Migration, interface change, anything with rollout order | Full structure plus explicit rollout notes |
| Refactor with no behaviour change | What and why, plus an explicit statement that behaviour is unchanged and how you know |
That last row is worth calling out. “Pure refactor” is a claim reviewers rely on to read the diff more quickly, and it is a claim worth backing: “no behaviour change; existing tests unmodified and passing” tells a reviewer both what to expect and what would falsify it.
The general rule: the description should be proportional to what the reviewer cannot infer, not to the size of the diff. A large mechanical rename needs less prose than a small change to a permission check.
Reading the summary as a signal
Section titled “Reading the summary as a signal”The generated description is a compressed view of your branch, which makes it a useful early warning.
If the summary mentions changes you had forgotten, you have found something worth checking before review.
If it reads as a list of unrelated things, the pull request is doing more than one job. Splitting it now costs less than a reviewer discovering it later — see PR best practices.
If it cannot say what the change is for, either you did not supply the reason or the reason is not clear enough to state. Both are worth resolving before somebody else has to ask.
If the risk section is empty on a change touching data or interfaces, either the model missed something or you got lucky. Worth a second look either way.
None of these requires trusting the summary. They are patterns in the output that point at something in the branch, and the branch is what you check.
Automating it
Section titled “Automating it”Most surfaces will generate a description directly: the GitHub pull request interface, Copilot in an editor, or Copilot CLI reading the branch diff itself.
A pull request template composes well with generation rather than competing with it. The template
supplies the headings; generation fills the parts derived from the diff; you fill the rest. A template
with a ## Why section that is visibly empty is a much stronger prompt to write one than a blank box.
See PR templates.
Where descriptions are generated automatically at pull request creation, one caution: an automatic description that nobody edits trains reviewers to skip descriptions, which is worse than having none. Generate on request, or generate into an editable draft.
For reviewers, not for the record
Section titled “For reviewers, not for the record”One framing shift that improves descriptions more than any prompt: write for the person who will read this in twenty minutes, not for posterity.
A pull request description has a short, intense readership — one to three people, once, deciding whether to approve. Optimising for that changes what belongs in it:
Front-load the decision-relevant part. A reviewer reads the first two lines properly and skims the rest. “Where to look” belongs high.
Say what you are unsure about. “I am not certain the timeout default is right — opinions welcome” directs attention better than any amount of confident prose, and it is exactly the kind of thing a generated description will never contain unless you add it.
Omit what the diff shows. File lists, function names, line counts. GitHub renders all of it.
Keep the permanent record elsewhere. If the reasoning matters in a year, it belongs in a commit message body, an ADR or a document — not only in a pull request description, which is findable in principle and unfindable in practice.
That last point is worth pairing with the history analysis lesson: reconstructing why a system is shaped a certain way works much better when the reasons were written into commits rather than into pull request threads that nobody thinks to search.
Common mistakes
Section titled “Common mistakes”Generating from commits when the branch was messy. Reverted work appears in the summary and not in the diff.
A description that restates the diff. Reviewers can read the diff. They cannot read your reasons.
Omitting “where to look”. The single highest-value line, and the most commonly missing.
Letting AI supply the issue number. It will produce a plausible one.
Not mentioning removed tests. A weakened assertion is a change to the safety net.
Treating generated risk assessment as complete. It sees the diff, not your deployment.
Using a good description to justify a bad pull request. If the description needs three sections for three unrelated changes, split it.
Mental model
Section titled “Mental model”The diff answers what. The description answers why, where to look, and what to worry about. AI writes the first well and can only relay the rest — so a description is a collaboration, not a generation.
What you learned
Section titled “What you learned”- Generate from
main...HEAD, the diff a reviewer sees, rather than from commit messages - Commits describe the journey and can include work that was later reverted
- “Where to look first” is the highest-value line in any description
- Asking whether anything looks unrelated turns generation into a check on your own branch
- Removed or weakened tests belong in the description explicitly
- Supply the issue number yourself; closing keywords are deterministic and act on merge
- A generated description cannot assess business risk or know your deployment process
- A pull request needing three unrelated sections should be three pull requests
Exercise
Section titled “Exercise”Use a branch with a few commits on it.
-
Generate a description from
git log --oneline main...HEADalone. Predict: does it describe anything that is no longer in the diff? -
Generate from
git diff main...HEAD. Compare. -
Add one sentence of intent and regenerate. Predict: which sections improve?
-
Add “if anything in the diff looks unrelated to that purpose, say so”. Predict: does it find anything?
-
Ask specifically which tests changed and whether any assertions were weakened. Check the answer against the diff.
-
Ask it to include an issue reference without telling it the number. Predict: what happens?
-
Write the final description, keeping only what a reviewer could not get from the diff.