One Claude Code Tip a Day: Draft the Commit from the Diff
Use Claude Code to draft commit messages from the actual git diff: inspect intent, split noisy changes, verify tests, then produce a message that tells the review story.
This post is part of the “One Claude Code Tip a Day” series — a daily guide to using Claude Code more effectively.
A weak commit message is usually a symptom of a weak final review. The code may pass tests, but nobody can tell what changed, why it changed, or which risk was checked. Claude Code is useful here, but not if you ask it to “write a commit message” from memory. The better habit is to make Claude read the actual diff, separate intent from noise, and draft the message only after the verification evidence is on the table.
The real failure: a polished message for a messy patch
Imagine you asked Claude Code to fix a checkout bug: coupon codes were accepted in the UI but dropped when the order was created. After two rounds, the tests pass. The diff touches the checkout form, the API payload mapper, a test fixture, and—accidentally—a formatting change in an unrelated analytics file. If you now ask “write a good commit message,” Claude may summarize the happy path and hide the accident: “Fix coupon submission in checkout.” That is readable, but it does not protect the reviewer.
The commit-message workflow should feel like a miniature code review. First inspect the diff. Then ask Claude to name the behavioral change, the supporting test change, and any unrelated edits. Only then ask for the message. The output should be a review aid, not a marketing headline.
The command: give Claude the diff as ground truth
Start from a clean checkpoint inside the repo. Run the checks yourself or have Claude run them with shell commands, then bring the evidence into the session:
git status --short git diff --stat git diff pnpm test -- checkout
In Claude Code, the prompt I use is deliberately restrictive:
Read the current git diff. Do not edit files. First summarize the behavioral change in one paragraph. Then list: 1) files that are part of the intended fix, 2) files that look incidental or risky, 3) tests or commands that prove the change. After that, draft a Conventional Commit message with a short subject and a body.
That “do not edit files” line matters. At the commit stage, Claude’s job is not to improve the patch by surprise. Its job is to make the patch legible. If it discovers a real problem, it should say so before touching anything.
What a good first pass looks like
For the coupon bug, a useful Claude response might say: the intended behavior is that the checkout submit handler now includes couponCode in the create-order payload; the mapper now preserves the field; the regression test asserts the field reaches the mocked API. It should also flag the analytics formatting change as unrelated. That flag is the value of the workflow. A human reviewer would rather see “remove unrelated analytics formatting from this commit” before the commit is created.
A solid draft might look like this:
fix(checkout): include coupon code in order creation Pass the checkout couponCode through the submit handler and order payload mapper so accepted coupons are preserved when creating an order. Add a regression test covering the API payload. Verified with: pnpm test -- checkout
Notice the message is not trying to document every line. It explains the behavior, the implementation boundary, and the verification command. That is enough for a teammate scanning history next month.
Second pass: make Claude challenge the commit boundary
The best moment in this workflow is the second pass. After Claude drafts the message, ask it to critique the commit itself:
Before I commit, review the diff as if you were blocking the PR. Which changes do not belong in this commit? Which file should I revert or split? Is the commit message claiming anything the diff does not prove?
This catches common AI-session residue: renamed variables that are not necessary, snapshot churn, formatting in untouched files, and tests that pass only because a mock was weakened. If Claude identifies the analytics formatting change, either revert it or make a separate commit. Then run git diff --stat again and ask Claude to update the message against the smaller diff.
Failure modes to watch for
First, Claude may overfit to your original task instead of the actual diff. If the patch changed validation and telemetry, but the prompt says “coupon bug,” it may omit telemetry entirely. Force it back to the diff: “Ignore my description. What does the diff actually change?” Second, Claude may produce a beautiful Conventional Commit subject with a vague body. Ask for concrete file-level evidence and the exact verification command. Third, it may include secrets, customer names, or internal incident details copied from logs. Treat commit messages as public-ish project history and redact accordingly.
Another trap is using Claude to justify a patch you have not reviewed. A commit message can make a risky diff sound coherent. Do not let language quality substitute for inspection. If the message is easy to write but the diff is hard to explain, that is a signal to split or simplify the commit.
A practical commit loop
My default loop is: run tests, inspect git diff --stat, ask Claude for a read-only diff summary, remove unrelated changes, ask for the commit message, then compare the final message against git diff one more time. For larger work, I ask for two versions: a terse Conventional Commit and a slightly longer PR-style body. The shorter one becomes the commit; the longer one often becomes the pull request description.
Rule of thumb: do not ask Claude Code to invent a commit message. Ask it to audit the diff and write the message from evidence. A good commit message is the receipt for a verified change: what behavior moved, where the patch lives, how it was checked, and what was intentionally left out.