The most useful AI prompt for a commit message or pull request gives the model the diff plus the context the diff cannot show (the ticket, the bug, the reason you chose this approach) and asks for a fixed shape: a short imperative summary line, then a body that explains why. For pull requests, the prompt should also ask for how the change was tested and where the reviewer should look hardest. A model working from the diff alone can describe what changed; only you can tell it why, so the prompt has to make room for that.
This guide gives copy-ready prompts for both jobs, shows a worked example from a raw diff to a finished pull request description, and covers the formats (plain Git convention, Conventional Commits, GitHub templates) the output should fit.
Commit messages and PR descriptions serve different readers
A commit message is read months later by someone running git log or git blame and asking why a line exists. A pull request description is read today by a reviewer deciding whether the change is safe to merge. The same facts feed both, but the emphasis differs.
| Commit message | Pull request description | |
|---|---|---|
| Main reader | A future maintainer reading history | A reviewer looking at the change now |
| Main question | Why does this code exist? | What should I check, and is it safe? |
| Scope | One logical change | One or more commits that ship together |
| Typical length | One summary line plus a short body | A few short sections |
| Must include | The reason for the change, trade-offs | Summary, motivation, testing done, risk areas, linked issue |
| Lifespan | Permanent, travels with the code | Lives on the hosting platform; squash merges may copy it into history |
Keeping the two separate in your prompts matters. If you ask for “a commit message and PR description” in one breath, you usually get the same paragraph twice.
What the conventions say a good message contains
The format the model should produce is not a matter of taste; it is documented. The git commit documentation recommends starting with a single summary line of no more than 50 characters, then a blank line, then a fuller description. Git treats the text up to the first blank line as the commit title and uses it throughout its tooling, which is why a rambling first line makes git log --oneline hard to read.
Google’s engineering practices guide on writing change descriptions adds the parts a model tends to skip. The first line should be an imperative sentence that says what the change does (“Delete the FizzBuzz RPC”, not “Deleting the FizzBuzz RPC”). The body should state the problem, why this approach was chosen, and any shortcomings, plus background like bug numbers or benchmark results. The guide also warns that links can go dead, so the description should make sense without them. Its list of bad examples (“Fix bug”, “Fix build.”, “Phase 1.”) reads like the default output of a lazy prompt.
That gives a checklist your prompt can enforce:
- Summary line: imperative mood, about 50 characters, no trailing period.
- Body: the problem, the reason for this approach, any known limitation.
- References: issue or ticket ID, plus enough context that the message still works if the link breaks.
- No narration of the diff line by line. The diff already shows what changed.
The core prompt for a commit message
This prompt is built around one rule: the model may describe what the diff does, but the reason has to come from you. Paste the staged diff (for example the output of git diff --staged) and fill in the context block.
You are writing a Git commit message for the staged diff below.
Context the diff does not show:
- Why this change was needed: <one or two sentences, or the bug report>
- Ticket or issue: <ID, or "none">
- Alternatives considered and rejected: <optional>
Rules:
1. First line: imperative mood ("Add", "Fix", "Remove"), at most 50
characters, no period at the end.
2. Blank line, then a body wrapped at 72 characters.
3. The body explains WHY the change was made and any trade-off or
limitation. Do not restate the diff line by line.
4. If the context above does not give a reason, write
"WHY: <missing>" in the body instead of inventing one.
5. End with a footer line "Refs: <ticket>" if a ticket was given.
Return only the commit message, no commentary.
<diff>
PASTE THE DIFF HERE
</diff>
Why each part is there:
- The context block exists because the reason for a change is almost never in the diff. Without it, models fill the body with paraphrase (“Updated the function to handle the new parameter”).
- Rule 4 is the most important line. It turns a silent guess into a visible gap you have to fill, which is the difference between a message you can trust and one that merely sounds plausible. This is the same idea covered in reducing AI hallucinations with better prompts: give the model an explicit way to say “I don’t know”.
- The diff tags separate your instructions from pasted content, so a code comment that happens to contain an instruction is less likely to be followed as one.
- “Return only the commit message” makes the output safe to pipe straight into
git commit -F -after you read it.
Variant: Conventional Commits
If your team uses Conventional Commits, replace rule 1 with the spec’s header format. The header is type(optional scope): description. fix corresponds to a SemVer patch release and feat to a minor release; a breaking change is marked either with ! after the type or scope, or with a BREAKING CHANGE: footer, and maps to a major release. Other types such as docs, refactor, test and ci are allowed but carry no version meaning on their own.
1. First line: Conventional Commits header, "<type>(<scope>): <description>".
Allowed types: feat, fix, docs, refactor, perf, test, build, ci, chore.
Scope: one of <api, web, db, infra>. Description in imperative mood,
lowercase, total header at most 72 characters.
If the change breaks a public interface, add "!" after the scope AND a
footer "BREAKING CHANGE: <what callers must change>".
If you are unsure whether it is breaking, say so in one line above the
message instead of guessing.
The last instruction is there because the type choice has consequences when release tooling reads it. A model that labels a breaking API change as fix produces a patch release that breaks callers. Asking it to flag uncertainty costs one line and puts the decision back with a human.
Splitting a messy diff first
When the staged diff mixes several unrelated changes, no message will be good, because there is no single reason to explain. Ask for a split plan before asking for messages:
Here is a diff that may contain more than one logical change.
List the separate logical changes you see. For each, give:
- a one-line summary
- the files and hunks that belong to it
- whether it depends on another change in the list
Do not write commit messages yet.
Then stage each group separately (git add -p is built for this) and run the commit prompt once per group.
The pull request description prompt
A pull request description has a different job: shorten the reviewer’s time to understanding. Reviewers lose the most time reconstructing the motivation, guessing what was tested, and hunting for the risky part of a large change. The prompt below asks for exactly those things and nothing decorative.
Write a pull request description for the changes below. The reader is a
reviewer who has not seen the ticket.
Inputs:
- Ticket / issue: <ID and one-line summary>
- Problem being solved: <in your words>
- How I tested it: <commands run, environments, manual checks>
- Things I am unsure about: <optional>
- Commit list: <output of git log --oneline main..HEAD>
- Diff: <diff or diff summary>
Output these sections in Markdown, in this order:
## Summary - one sentence: what this PR does.
## Why - the problem and why this approach, 2-4 sentences.
## Changes - bullet list grouped by area, each bullet says what
and why, not file-by-file narration.
## How to test - steps a reviewer can run, based only on what I listed.
## Review focus - the 1-3 places most likely to hide a bug or where I
made a judgment call, with file paths.
## Risk - deployment, migration, config or backwards-
compatibility concerns. Write "None identified" only
if nothing in the diff touches these.
Closes #<issue>
Rules:
- Do not claim any test was run that is not in "How I tested it".
- If the diff touches auth, payments, data deletion, migrations or
public APIs, list it under Review focus even if I did not mention it.
- Keep the whole description under 300 words.
The design choices that make this speed up review:
- “Review focus” is the highest-value section. It tells the reviewer where to spend attention, which is the main thing a reviewer cannot get from the diff. The rule that forces sensitive areas into it protects against the author’s own blind spots.
- The testing rule stops the model from writing “All tests pass” when you never said so. A fabricated testing claim is worse than none, because the reviewer stops checking.
- The commit list gives the model the story of the change in order, which helps it group bullets by intent rather than by file.
- The word limit keeps descriptions short enough to read. A long AI description that nobody reads does not speed anything up.
- The closing keyword links the PR to its issue. GitHub recognizes keywords such as
closes,fixesandresolves, but per its documentation on linking pull requests to issues, they only take effect when the pull request targets the repository’s default branch. For a PR into a release branch, use a plain reference instead.
Worked example: from diff to reviewable PR
The following example is illustrative. Imagine a small service where users reported that password-reset links sometimes expired immediately. The developer found that token expiry was compared in local server time while tokens were stored in UTC, and changed it.
The context the developer types in:
Ticket: AUTH-412 "Reset links expire instantly for some users"
Problem: expiry check compared a UTC timestamp to local time, so on
servers not in UTC, tokens could be treated as expired on creation.
How I tested: added unit tests for expiry at UTC+0, UTC-5 and UTC+9;
ran the auth test suite locally. Did not test on staging.
Unsure: whether old tokens created before the fix behave correctly.
A vague prompt (“write a commit message for this diff”) tends to produce something like this:
Update token validation
Updated the is_expired function to use datetime.now(timezone.utc)
instead of datetime.now(). Also added tests.
That message restates the diff and loses the reason. With the core prompt and the context above, the output looks more like this:
Compare reset-token expiry in UTC
Tokens are stored with UTC timestamps, but is_expired() compared them
with naive local time. On hosts not running in UTC, a token could be
treated as expired as soon as it was created.
Use timezone-aware UTC time for the comparison and add tests covering
hosts at UTC-5, UTC+0 and UTC+9.
Tokens issued before this change keep their stored UTC expiry, so no
migration is needed, but this has not been verified on staging.
Refs: AUTH-412
And the pull request description’s most useful part, the review focus and risk sections:
## Review focus
- auth/tokens.py: is_expired() now uses an aware datetime. Any other
caller that passes a naive datetime will raise TypeError instead of
silently comparing wrong values. I found none, but please check.
- Tokens created before deploy: not tested on staging (see Risk).
## Risk
Touches authentication. No schema change. Behavior changes only on
hosts whose local time is not UTC.
Notice what the model did not do: it did not claim staging tests, and it surfaced the naive-versus-aware datetime risk, which is the kind of thing a reviewer needs to hear about before approving. The “unsure” line from the developer became a review item instead of disappearing.
Fitting the output into your team’s template
If your repository already has a pull request template (on GitHub, a pull_request_template.md file, commonly kept in .github/), paste it into the prompt and tell the model to fill it rather than inventing its own sections. A short template that mirrors the sections above gives humans and the AI the same target, and reviewers learn where to look.
Built-in summary buttons in code hosting tools work from the diff, so they share the limit described at the top: they can cover the what, while the why, the testing and the review focus still need your input. Treat any automatic summary as a draft to edit. If your team uses an AI assistant in the editor, a repository instructions file is a good place to state your commit and PR conventions once; see writing a Copilot instructions file.
Mistakes that make AI-written messages worse than none
- Accepting an invented reason. The model will produce a plausible “why” if you do not supply one. Plausible and true are different, and a wrong reason in permanent history misleads the next maintainer.
- Narrating the diff. “Changed X to Y in file Z” adds nothing a reviewer cannot see. Delete such lines.
- One message for an unrelated bundle. If the model has to write “and also”, split the commit.
- Stale descriptions. Pull requests change during review. Google’s guide recommends rereading the description before submitting; re-run the PR prompt on the final diff if the scope moved.
- Pasting secrets. Diffs can contain keys or customer data. Strip them before sending the diff to any external model, and keep a scanner in your pipeline; the walkthrough on Gitleaks pre-commit hooks covers one way to do that.
- Skipping review because the description is good. A clear description makes review faster; it does not replace it. Pair it with the prompts in AI code review prompts if you want a structured first pass on the code itself.
A short checklist before you commit or open the PR
- Does the summary line say what the change does, in imperative mood, in about 50 characters?
- Does the body give a reason you actually know to be true?
- Is any “WHY: missing” placeholder still there?
- Does the PR description list only tests you really ran?
- Does “Review focus” point at the riskiest code, with paths?
- Is the issue linked with a keyword that will work for the target branch?



