Write a Pull-Request Description That Links the Change to Evidence

Drafted and reviewed using AI assistance.

On this page

A useful pull-request description connects a concrete problem to the patch, then says what was actually checked. Let the reviewer distinguish an implemented change, an expected outcome, and an observed result without reconstructing that distinction from a long conversation.

Start with a small fact pack. Write the description from those facts, then remove any sentence whose confidence exceeds its evidence. A planned check belongs in a plan. A recorded result belongs in validation. A local commit, a merged change, and a deployed feature each require their own status.

This guide uses a fictional TypeScript export patch. All files, requirements, descriptions, and review requests are original examples. The code has not been executed, and no test, CI, merge, or deployment result is claimed.

Build the fact pack before writing the summary

The fictional application exports a set of rows that an upstream layer has already authorized. A new option is intended to let a caller include closed rows from that authorized set. The export selector previously removed those rows unconditionally.

Here is the complete small source example for src/export/selectRows.ts before the patch:

export type Row = {
  id: string;
  status: "open" | "closed";
};

export function selectRows(rows: readonly Row[]): Row[] {
  return rows.filter((row) => row.status !== "closed");
}

And the replacement selector:

export type Row = {
  id: string;
  status: "open" | "closed";
};

export function selectRows(
  rows: readonly Row[],
  includeClosed = false,
): Row[] {
  return rows.filter((row) => includeClosed || row.status !== "closed");
}

The example's previous caller, src/export/buildExport.ts, selected rows without forwarding an option:

import { type Row, selectRows } from "./selectRows";

export function buildExport(
  authorizedRows: readonly Row[],
  options: { includeClosed?: boolean },
): Row[] {
  return selectRows(authorizedRows);
}

The replacement caller forwards the typed option:

import { type Row, selectRows } from "./selectRows";

export function buildExport(
  authorizedRows: readonly Row[],
  options: { includeClosed?: boolean },
): Row[] {
  return selectRows(authorizedRows, options.includeClosed);
}

Finally, the fictional patch adds this paragraph to docs/export.md:

The export builder accepts an optional includeClosed boolean. When omitted
or false, selection excludes closed rows. When true, selection retains
closed rows from the already-authorized input set. The selector does not
fetch additional rows or perform authorization.

That is the whole patch for this teaching example. It adds no export button, request parser, file download, or authorization implementation. Those omissions matter when deciding how broadly to describe the resulting behavior.

Record the evidence before it gets mixed with expectations:

ID Material What it supports
S1 Replacement selectRows.ts above New boolean parameter and false default; closed-row predicate
S2 Replacement buildExport.ts above Caller forwards its typed option
D1 New docs/export.md paragraph Documentation of selection semantics and boundary
V1 Proposed checks below A validation plan only; no execution result
R1 Example requirement: optionally retain closed authorized rows Intended scope, not evidence that the implementation works

The upstream authorization boundary is a scenario assumption. These snippets do not demonstrate that an actual application enforces it. Likewise, a TypeScript annotation alone does not validate arbitrary runtime input arriving over a network.

See what goes wrong in the old description

An overconfident first draft might read:

Fix all missing export records.

Exports now include everything, with full backward compatibility and
complete permission safety. All tests pass across browsers. The fix is
live and ready for customers.

Every sentence makes the patch harder to evaluate. “All missing export records” hides the specific closed-row condition. “Everything” ignores both the option's default and the authorized input boundary. “Full backward compatibility” overstates what a few snippets can establish. Browser testing and release status have no supporting records at all.

The correction is to narrow the subject and state the validation status plainly. More technical detail will not repair an unsupported conclusion.

Use a complete replacement PR description

Here is a description written from the fact pack. In a real PR, link the file references to the relevant diff and attach actual check records when available. The paths below refer to the fictional files displayed in this article.

## Problem and change

The export builder accepts an includeClosed option, but the previous
caller did not forward it and selection always excluded closed rows.
This patch forwards the option and adds a selector parameter intended
to retain closed rows when includeClosed is true.

The selector defaults to false, preserving the previous exclusion rule
for calls that omit the option. It operates only on the rows it receives.

## Relevant files

- src/export/selectRows.ts: adds includeClosed and updates the predicate.
- src/export/buildExport.ts: forwards options.includeClosed.
- docs/export.md: describes default behavior and the input boundary.

## Validation

No compilation, automated tests, browser checks, or end-to-end export
checks have been run for this illustrative patch. The behavior described
above is inferred from the displayed source, not recorded execution.

Planned checks:
- Mixed open/closed input with the option omitted and set to false.
- The same input with the option set to true.
- Empty input and the caller forwarding each supported value.
- The existing upstream authorization boundary in the real application.

## Scope and remaining work

This patch does not add a UI, parse untrusted request values, change
authorization, or fetch records beyond its input. It does not establish
that a complete downloadable export works. Merge and deployment have
not occurred in this example.

Please review the default and forwarding behavior before expanding the
change into any separate request-parsing or interface work.

This is a complete description even though its validation section contains no successful run. It tells the reviewer what is present, where to inspect it, and what confidence is still missing.

Map the final claims back to evidence

Check the description sentence by sentence, especially after an agent rewrites it for fluency.

Description claim Evidence Boundary to preserve
Previous caller did not forward the option Previous caller snippet Describes this fictional patch, not a reported production incident
Selector adds a false default S1 Source-level observation; no compiled artifact checked
Caller forwards the option S2 Does not prove a UI or API sends the value
True is intended to retain closed input rows S1 and R1 Expected behavior, not an executed test
Selector does not fetch more rows S1 No conclusion about upstream data access
No checks have run Explicit execution status V1 must remain a plan
No deployment occurred Example's delivery status A future merge should not silently rewrite this as live

The mapping can stay in a review note rather than the public description. Its purpose is to help an author avoid turning an assumption into a fact while shortening the text.

When checks run later, update the relevant sentence

A validation update should name the revision, environment, command or procedure, result, and evidence location. If only selector checks ran, describe selector checks. Leave the end-to-end export status unresolved until there is corresponding evidence.

For example, replace “not run” only for the check that now has a record. Do not replace it with “fully tested” because one suite passed. If new code changes the option's semantics after that run, the old result may no longer support the current revision.

GitHub's PR lifecycle guidance discusses early review of drafts and re-review after substantial changes. The description format here is our editorial recommendation for making the current change understandable throughout that process; it is not a required GitHub template.

Copy this shorter template for your own patch

## Problem and resulting behavior
[Concrete trigger and previous behavior. What this patch changes.]

## Evidence in the patch
[Relevant files or diff sections and the claim each supports.]

## Validation
[Actually performed checks, revision/environment, result, evidence link.]
[Checks not run and any practical limitation.]

## Remaining scope
[Important behavior this patch leaves for separate work.]
[Current merge/deployment status when relevant.]

## Review focus
[The decision or risk for which reviewer judgment is most useful.]

Write the description for someone who has not seen the agent conversation. They should be able to understand the problem and judge the available evidence from the PR itself.