Pull request template: the fields nobody fills in
Table of contents
Here is a pull request description from a real repository. The template was added six months ago by someone who cared.
## Description
N/A
## Type of Change
- [x] Bug fix
## Screenshots
N/A
## Checklist
- [x] I have performed a self-review of my code
- [x] New and existing unit tests pass locallyNothing here is wrong, exactly. Every box is ticked. The author complied. And the reviewer learned nothing they could not have learned from the diff in four seconds. When a review has nothing substantive to engage, the comments drift to the one thing everyone can judge, the formatting, which is Sayre's law playing out inside a diff: the trivial gets the attention the logic deserved.
This is the normal outcome. A team notices reviews are slow, adds a pull request template copied from a blog post, and six months later the template is a formality that everyone completes and nobody reads. The usual diagnosis is that people are lazy. The actual cause is in the template: it asks the author for information the reviewer already has.
What is a pull request template?
A pull request template is a markdown file in your repository that pre-fills the description box whenever someone opens a new pull request. On GitHub it lives at .github/pull_request_template.md. The platform reads it, drops its contents into the empty description field, and the author writes over the prompts. The same .github/ directory holds issue templates, the intake-side sibling that shapes bug reports the way this one shapes pull requests.
That is the whole mechanism. It is a default value for a text box, and the platform vendors document it well: GitHub's own guide covers the setup in about four steps, as does Microsoft's for Azure DevOps.
What none of them tell you is what to put in it. That question is not a formatting question, and it is the one that decides whether the template survives contact with a busy Tuesday.
Why pull request templates fill up with N/A
A pull request arrives carrying two kinds of information. The first kind is already in the diff, and the reviewer can see it without being told. The second kind exists only in the author's head, and if they do not write it down it is gone.
Most templates ask almost entirely for the first kind.
| The reviewer can already see this | Only the author knows this |
|---|---|
| Which files changed | Which approach was tried first and abandoned |
| That it is a bug fix, from the one-line change | Whether the bug had a second cause that is still there |
| That tests were added, from the test file | Which case the tests deliberately do not cover |
| The new endpoint's shape | Why it is a new endpoint instead of a parameter |
| That a migration file exists | Whether the migration is safe to run while traffic is live |
Read the left column again. "Type of Change: Bug fix" is on it. So is "Screenshots" for a backend service that has no user interface. So is most of the standard checklist.
There is a second source of N/A, and it's worse because it looks responsible: checklist items that duplicate continuous integration. Tests pass locally. Lint passes. No debug statements left in.
If a machine can check it, a machine should. Asking a human to promise it produces exactly one outcome, which is that the human ticks the box without looking. That isn't a safety net. It's a ritual that trains people to tick boxes without reading them, which is a strange thing to teach anyone who also reviews code.
The third source is scope. Most templates are written for the worst pull request the team has ever received, so a one-line copy fix inherits a form designed for a database migration. The author looks at eight prompts, four of which are irrelevant, and reasonably concludes that the whole thing is decoration.
Start writing in Scribelet if you want somewhere to keep the reasoning that does not fit in a diff. More on that at the end.
A pull request template you can copy
Here is a default that is deliberately shorter than the one you will find in most guides. Every prompt asks for something the diff cannot answer.
## What and why
One or two sentences. What changes, and what problem that solves.
Link the issue if there is one.
## Approach
What you chose, and what you rejected. Skip if the change is obvious.
## Risk
What breaks if this is wrong, and how you would know.
Write "low, reversible" when that is true. It usually is.
## Testing
What you actually ran. Not what CI runs.
Call out anything you could not test.Four prompts. Roughly forty seconds of work for a small change and five minutes for a serious one, which is the correct ratio.
The reasoning behind each one:
- What and why exists because six months from now this description is the only record of intent that will still be attached to the code.
git blamefinds the pull request, and the pull request should say something. - Approach is the field that pays for the template on its own. "I tried a database constraint first, it deadlocked under the importer's write pattern, so this validates in the service layer" turns a reviewer's objection into a conversation that has already happened.
- Risk is there because authors underestimate it silently and reviewers cannot see the absence of a concern. Writing "low, reversible" costs nothing and is genuinely informative when it is wrong.
- Testing asks what you ran, not whether tests exist. The test file already answers the second question.
Notice what is missing: no type-of-change checkboxes, no screenshots section, no checklist repeating CI. Those are in the section on what to delete, further down.
Google's code review guide makes the same point from the reviewer's side. What a reviewer is asked to judge is design, complexity, and whether the change makes the system better overall. None of that is visible in a checkbox.
Three pull request templates instead of one
The reason a single template fails is that a typo fix and a schema migration are not the same event, and no set of prompts serves both. Both GitHub and GitLab support multiple templates, and almost nobody uses the feature.
Put several files in .github/PULL_REQUEST_TEMPLATE/ instead of one at the root, then select one by adding ?template=migration.md to the pull request URL. Most teams link the two or three they use from their contributing guide.
Bug fix. The default above, minus Approach. Add one prompt that matters more than anything else in a fix:
## Root cause
What was actually wrong. Not the symptom.
## Why it was not caught
Test gap, monitoring gap, or a case nobody considered.That second prompt is the one that turns a fix into a lesson. It also produces, at no extra cost, the raw material for the incident write-ups and the runbooks somebody will otherwise reconstruct from memory a week later.
Feature. The default, plus the pointer to the thinking that already happened:
## Design
Link the design doc, spec, or ADR. If there is not one, say why not.If the change is large enough to need that link, it was large enough to need a design doc or a technical spec before the code. When the answer to "why not" is "this got big halfway through", that is worth knowing during review rather than after.
Migration or revert. The one place a checklist earns its keep, because the items are genuinely not machine-checkable, the parts of your definition of done a human still has to confirm:
## Rollout
- [ ] Safe to run while traffic is live, or the downtime window is booked
- [ ] Backwards compatible with the currently deployed code
- [ ] Rollback tested, not assumed
## Blast radius
What data this touches and how much of it.Every one of those requires a human to think. That is the test for whether a checkbox belongs in a pull request template at all.
Where the pull request template file goes
The mechanics differ by platform in ways that are easy to get wrong, mostly around casing and directory names.
| Platform | Single template path | Multiple templates | How one is selected |
|---|---|---|---|
| GitHub | .github/pull_request_template.md | .github/PULL_REQUEST_TEMPLATE/ directory | ?template=name.md in the URL |
| GitLab | .gitlab/merge_request_templates/Default.md | Same directory, one file each | Dropdown in the merge request form |
| Bitbucket Cloud | Repository settings, not a file | Not supported | Applied to every pull request |
| Azure DevOps | .azuredevops/pull_request_template.md | pull_request_template/ subdirectory | Branch-specific, or ?template=name |
Two details that cost people an afternoon. On GitHub the single-file name is lowercase while the directory name is uppercase, and both work only in .github/, the repository root, or docs/. On Azure DevOps templates can be bound to a target branch, so a template can apply to pull requests into main and not into a feature branch.
The fields to delete
If you already have a template and it is being ignored, this section is the whole intervention. Deleting is faster than rewriting and it works better.
- Type of change checkboxes. The diff says. In the rare case it does not, the title does.
- Anything continuous integration already enforces. Tests pass, lint passes, build succeeds, no debug statements. Let the machine assert it.
- Screenshots, unless the repository has a user interface. A required screenshots section on a backend service is where the N/A habit is born, and the habit spreads to the fields that matter.
- "I have performed a self-review of my code." Nobody has ever ticked this box honestly and then unticked it.
- Long contribution reminders. Link the contributing guide instead of pasting it into every pull request.
- Emoji section headers, if the team reads pull requests in a terminal or in email digests, where they render as boxes.
The rule underneath all six: a prompt earns its place only if a thoughtful author could write something surprising in it. If every honest answer is identical, the field is decoration, and decoration is what teaches people to stop reading the template.
Your pull request template goes stale too
A template is a standing instruction. It gets written once, applied thousands of times, and reviewed almost never, which makes it one of the purest examples of documentation that quietly stops being true.
The failures are specific and they all look like this:
- It asks for a QA sign-off from a QA team that was reorganised last year.
- It links a staging environment at a URL that now redirects to a login page.
- It requires a Jira ticket number, six months after the team moved to Linear.
- It asks contributors to update the wiki, and the wiki has been read-only since the migration.
None of these break anything. That is the problem. The pull request still opens, the box still gets ticked, and the instruction that no longer means anything is followed by a hundred people who assume somebody checked. This is the same failure mode as internal documentation nobody owns and the reason an architecture decision record needs a supersession discipline rather than a review calendar.
Two things fix it, and neither is a quarterly review meeting.
Give the template an owner and a last-verified date. Put a comment at the top of the file recording when someone last confirmed every link and process reference in it still resolves. Last edited is not last verified. A template edited last month to add an emoji is not a template anyone has checked.
Attach the check to a trigger, not a calendar. The template needs re-reading when the ticket system changes, when the QA process changes, or when a linked environment moves. Those are the events that invalidate it, and they are far more reliable prompts than "the first Monday of the quarter".
This is the part Scribelet is built for. A pull request template, like a runbook or an ADR, is a set of claims about the world that were true on the day they were written. Scribelet's background agents check the claims in your notes against current sources on a schedule you set, and show you a diff of what changed and why, so a dead link or a renamed process surfaces as a flagged change rather than as an instruction a hundred people quietly ignore. It is the same argument we make about knowledge decay everywhere else, applied to the one document your team reads more often than any other.
Try Scribelet free and keep the claims in your engineering docs under review.
The short version
A pull request template works when it asks for what only the author knows and stays quiet about everything else.
- Ask four things: what and why, approach, risk, testing.
- Delete every prompt the diff already answers and every box continuous integration already enforces.
- Use two or three templates by change type instead of one that fits nothing.
- Give the file an owner and a last-verified date, because it decays like any other document.
If your current template is being filled with N/A, that is not a discipline problem on your team. It is feedback, and it is telling you which fields to cut.
Share this article