Skip to main content
← Blog
13 min readThe Moxie Docs team

How to Write a Pull Request Description: A Template Reviewers and Agents Can Both Use

Most pull request descriptions restate the title or the prompt. A practical guide to writing PR descriptions that speed up review and survive in git history: what to include, a copy-paste GitHub template, examples, and how to keep agent-written descriptions honest.

  • documentation
  • developer-experience
  • engineering-management
  • ai-agents

The most-read piece of documentation in your repository is not the README. It's the pull request description. Every reviewer reads it before the diff, every future engineer running git blame lands on it eventually, and every coding agent that searches your history for "why is it like this" treats it as ground truth. Yet the typical pull request description is one line long, repeats the title, and says "fixes bug."

Google's engineering practices guide puts the stakes plainly: a change description "will become a permanent part of our version control history" and will be read by many people over the years, not only the reviewer today (Google, Writing good CL descriptions). A PR description is documentation with a very long tail and almost no maintenance cost — you write it once, while the context is still in your head, and it never goes stale because it describes a moment in time.

This guide covers how to write a pull request description that makes review faster: what a good one contains, a GitHub template you can drop into .github/, before-and-after examples, the specific way agent-written descriptions go wrong, and how to tell whether your team's descriptions are actually improving.

What a PR description is for#

A pull request description has three readers, and they want different things.

ReaderWhen they read itWhat they need
The reviewerToday, before the diffIntent, scope, where to look first, how it was tested
A future engineerMonths later, from git blameWhy this change was made and what was ruled out
A coding agentWhenever it searches historyWhich pattern is current, and which one this replaced
The release writerAt deploy or changelog timeUser-visible impact, flags, migrations
On-callDuring an incidentWhat changed in behavior, and how to roll it back

The diff already answers what changed, line by line. The description's job is everything the diff can't say: the reason, the alternatives you rejected, the risk, and what the reviewer should verify. A description that summarizes the diff in prose is redundant with the diff. A description that explains the decision is the only place that decision will ever be written down.

That's also why "the code should speak for itself" doesn't apply here. Code can say what it does. It can't say what it didn't do, or why the obvious approach was wrong.

The anatomy of a good description#

1. Write the title as an imperative sentence. "Retry failed webhook deliveries with exponential backoff," not "Webhook fixes" or "WIP." Google's guide recommends the first line be a short, complete, imperative sentence that stands alone in a log. On GitHub, the title becomes the squash-merge commit subject, so this line lives in git log --oneline forever. If you can't write the title in one sentence, the PR is probably doing two things.

2. Lead the body with why. One or two sentences on the problem, the user report, or the ticket. Link the issue, but don't only link it — issue trackers get migrated and archived, and git history outlives them. "Stripe retries webhooks for three days, but our handler returned 500 on transient DB errors, so events were retried into a still-broken state and then dropped" beats "Fixes #1842."

3. Summarize what changed as scope, not a file list. Three to five bullets describing the behavior change. The reviewer can see which files changed; they can't easily see that the change touches the billing path and the email path. If a bullet starts with "Also," consider whether it belongs in another PR.

4. Say how you verified it. The commands you ran, the case you tested by hand, a screenshot for UI changes, a before-and-after query for data changes. "Tests pass" is not verification — CI already says that. Verification is the thing you checked that CI doesn't cover.

5. Name the risk and the rollout. Is it behind a flag? Does it include a migration, and is it reversible? What does rollback look like? What breaks if this is wrong? Two sentences here save an on-call engineer twenty minutes at 3am.

6. State what's out of scope. Pre-empting the obvious review comment ("why not also fix X?") with "X is tracked in #1850" saves a round trip. It also tells future readers the omission was deliberate.

7. Note the docs. Which docs you updated, or which ones now describe old behavior. More on why this line matters below.

A pull request template you can copy#

GitHub picks up a template automatically from .github/pull_request_template.md (or the repository root, or docs/), and supports multiple templates under .github/PULL_REQUEST_TEMPLATE/ selected with a template query parameter (GitHub Docs). Keep it short enough that people fill it in rather than delete it:

MARKDOWN
## Why

<!-- The problem, in one or two sentences. Link the issue, but don't rely on it. -->

## What changed

-

## How I verified it

<!-- Commands run, manual cases checked, screenshots for UI. Not "CI passes." -->

## Risk and rollout

<!-- Flags, migrations, reversibility, what breaks if this is wrong. "Low: copy only" is a fine answer. -->

## Out of scope

<!-- Deliberately not done here, with a link if it's tracked. -->

## Docs

- [ ] Updated docs that describe this behavior
- [ ] No docs describe this behavior

A few rules for templates that actually get used:

  • Every section must allow a one-line answer. "Risk: low, copy change only" is a complete answer. Templates that demand essays get deleted wholesale.
  • Use HTML comments for prompts, not placeholder text. Comments don't render, so an unfilled section looks empty rather than looking filled with boilerplate.
  • Keep checklists to things a person can actually decide. "I have written tests" is theater. "Docs updated / no docs describe this" is a real decision with a real answer.
  • Don't duplicate CI. If lint, types, and tests are gated, don't ask the author to tick boxes asserting they pass.

Examples: weak, better, good#

The same change, three ways.

Weak:

MARKDOWN
Fix webhook bug

Better:

MARKDOWN
Retry failed webhook deliveries

Fixes #1842. Added retry logic to the webhook handler and updated tests.

Good:

MARKDOWN
Return 503 on transient DB errors in the Stripe webhook handler

## Why
Stripe retries webhooks on non-2xx responses. We returned 500 for every
failure, including permanent ones (unknown event type), so bad events were
retried for three days, and transient DB errors were indistinguishable from
real bugs in our alerts. Fixes #1842.

## What changed
- Transient DB errors (connection reset, lock timeout) now return 503 so
  Stripe retries them.
- Unknown event types return 200 and are logged, so they stop retrying.
- Alert on 500s only; 503s go to a separate low-priority metric.

## How I verified it
- Replayed the three failing events from #1842 with `stripe events resend`.
- Killed the DB connection mid-request locally; confirmed 503 and a
  successful retry.

## Risk and rollout
No migration. Revert is safe. If the classification is wrong, the worst case
is a permanent error being retried, which is today's behavior.

## Out of scope
Idempotency keys on the downstream email send; tracked in #1850.

## Docs
Updated `docs/billing/webhooks.md` retry section.

The good version is longer, but it takes a reviewer less time, because it tells them exactly what to check: the error classification. It also answers the question someone will ask in six months — "why do we return 200 for unknown events?" — without a Slack archaeology session.

Length should scale with risk, not with diff size. A one-line dependency bump needs a one-line description. A ten-line change to authentication needs a full one.

When the author is an agent#

Coding agents write fluent PR descriptions, and that's the problem. As we covered in reviewing AI-generated pull requests, an agent's description frequently restates the task it was given rather than summarizing what it actually did. The prompt said "add retry logic," so the description says "adds retry logic" — even if the agent also refactored the error types, deleted a flaky test, and changed a timeout.

SymptomWhat it usually meansFix
Description matches the prompt word for wordWritten before or without reading the diffGenerate it from git diff, not from the task
"Also improved…" or "cleaned up…"Silent scope creepSplit the PR, or list every extra change explicitly
No verification section, or "all tests pass"Nothing beyond CI was checkedRequire the agent to list commands it ran and their output
Confident claims about behavior not in the diffHallucinated summaryReviewer compares claims to diff before approving
No mention of docsDocs weren't consideredMake the docs line mandatory in the template
Every section filled, all genericTemplate satisfied, not answeredReject descriptions that could apply to any PR

The practical fixes live in the instruction file the agent reads on every task. A few lines in your AGENTS.md or CLAUDE.md go a long way:

MARKDOWN
## Pull requests
- Write the PR description from the final `git diff`, not from the task.
- List every behavior change, including ones you weren't asked to make.
- Under "How I verified it", list the exact commands you ran.
- One concern per PR. If you touched unrelated code, say so or split it.
- Fill the "Docs" section: name the doc you updated, or state that none apply.

If you don't have an instruction file yet, the free AGENTS.md generator will scaffold one you can add these lines to.

The line everyone skips: docs#

Of all the template sections, "Docs" is the one most often left blank, and it's the one with the longest consequences.

A PR that changes behavior without touching the doc that describes that behavior doesn't just leave docs incomplete. It makes them wrong, starting at merge. The webhook doc still says "we retry on 500." A new engineer reads it. A coding agent reads it, trusts it — docs read like statements of intent — and writes the next change against behavior that no longer exists. That's documentation drift, and the pull request is the only point in the lifecycle where it's cheap to prevent: the author knows exactly what changed, the reviewer is already looking, and the fix is one more file in the same diff. This is the core idea behind a docs-as-code workflow: the docs change ships in the same PR as the code change, or it probably never ships.

The hard part is that the author often doesn't know which docs describe the behavior they changed. Nobody greps the whole docs tree before opening a PR. A checkbox asks the question; it doesn't answer it.

That's the gap Moxie Docs is built for. It indexes your GitHub repository into living, source-cited documentation and serves it to coding agents over MCP (Model Context Protocol). Before an agent opens a PR, moxie.review_change checks the proposed files and returns clean, warnings, or must-fix — including docs the change makes factually false — and moxie.propose_doc_update returns the doc path and content for the agent to write into the same PR. On the PR itself, an advisory documentation check posts one edit-in-place comment, and when docs have already fallen behind, Moxie opens a Cleanup PR for your team to review — it proposes, you merge. The free plan covers one repository with no card required.

Measuring whether it's working#

You don't need a survey to know whether PR descriptions are getting better. Watch these:

  • Review rounds per PR. Good descriptions cut "what is this for?" comments. If average rounds drop after adopting a template, it's working.
  • Time to first review. Reviewers pick up PRs they can understand quickly. Opaque PRs sit.
  • The blame test. Pick five lines at random from recently changed files, run git blame, and open the PR. Can you tell why from the description alone?
  • Empty-section rate. Skim the last twenty merged PRs. Which template sections are routinely blank? Either people need a nudge, or the section should go.
  • Behavior changes with no docs line. Count PRs that changed behavior and left "Docs" empty. That's your documentation debt accrual rate.

None of these need a dashboard. Twenty minutes with the last month of merged PRs tells you most of it.

Frequently asked questions#

What should be included in a pull request description?#

At minimum: why the change is needed, what behavior changed, how you verified it, and any risk or rollout concerns such as migrations or feature flags. Add what's deliberately out of scope and whether docs were updated. Skip anything the diff already shows, like a list of changed files.

How long should a pull request description be?#

Proportional to risk, not diff size. A dependency bump or copy fix can be one line. A change to auth, billing, data, or a public API deserves the full template, even if the diff is ten lines. If the description needs more than a screen, the PR is probably too big.

How do I add a pull request template on GitHub?#

Create .github/pull_request_template.md in the default branch. GitHub pre-fills new PR bodies with it. For multiple templates, put them in .github/PULL_REQUEST_TEMPLATE/ and select one with the template query parameter on the compare URL.

Should the PR description and the commit message be the same?#

With squash merging, they effectively become the same: GitHub uses the PR title as the commit subject and can use the body as the message. Write the title as a standalone imperative sentence for that reason. With merge commits, individual commit messages still matter, and the PR description is the overview that ties them together.

Can AI write my pull request descriptions?#

Yes, and it's often good at it, as long as it works from the final diff rather than the original request. The failure mode is a confident summary of what the agent was asked to do. Require the description to list every behavior change and the commands actually run, and have a reviewer compare the description against the diff before approving.

The short version#

A pull request description is the one piece of documentation that's written at the exact moment the author knows everything and never has to be maintained afterward. Use it for what the diff can't say: why, what you ruled out, how you checked, what could break, and which docs changed with it. Give your team a template short enough to fill in, give your agents instructions to describe the diff instead of the prompt, and treat the docs line as a real decision instead of a checkbox. The diff tells a reviewer what changed. The description is the only place anyone will ever learn why.

Republish or cite this article

You're welcome to republish this piece in full or in part. We just ask that you credit the original with a link back. See our republishing guidelines.

Attribution snippet

<p>This article was originally published on <a href="https://moxiedocs.com/blog/how-to-write-a-pull-request-description">Moxie Docs</a>.</p>

Cite this article

The Moxie Docs team. "How to Write a Pull Request Description: A Template Reviewers and Agents Can Both Use." Moxie Docs, September 30, 2026, https://moxiedocs.com/blog/how-to-write-a-pull-request-description.

Try it on your repo

Put your own codebase on the same footing.

Searchable docs, MCP-ready context, and Cleanup PRs that keep everything current as the code changes.