Good commit messages explain why, and AI agents read them
Good commit messages say what broke, why, how it was verified and what was left alone. My template as a team lead, and why AI coding agents depend on it now.
By Hamza Ghouri
Good commit messages are the cheapest documentation a team writes. The diff already shows what changed; the message is the only place that says why it changed, how anyone knows it works, and what was left alone on purpose. As a team lead I lean on that history when reviewing, and AI coding agents now read it too. This post is the style I use, a template you can copy, and the reasons it pays off.
What good commit messages carry in the subject
The subject line says the change in plain words, as a short sentence a teammate would understand without opening the diff. A few real subjects from my own history:
fix(posts): reply before writing follower notificationsperf(db): add the indexes real queries need, drop a duplicatefix(cron): skip rows the app has locked when deleting old notifications
Each one starts with a type and a scope, which is the Conventional Commits format. The prefix makes git log --oneline easy to scan: fixes, performance work and features separate at a glance, and the scope tells you which part of the system moved.
The part after the colon is what matters most. "Reply before writing follower notifications" tells you the behaviour that changed. A subject like "update posts controller" tells you which file was touched, which the diff already shows.
My rules for the subject:
- Say what the system does differently now, in words a reader outside the code would follow.
- Use the imperative, as if completing "this commit will...".
- Keep it short enough to read in a one-line log.
The body: what broke, why, how it was verified, what was left alone
The body is where the commit earns its keep. Mine follows the same four parts every time:
- What broke. The symptom as a user or operator saw it.
- Why. The cause, in a sentence or two.
- How it was verified. What was run, on what data, and what came out.
- What was deliberately left alone. Anything nearby that looks related and was not changed, and why.
The fourth part is the one most people skip, and the one future readers need most. Without it, the next person sees an untouched neighbour, assumes it was missed, and "fixes" it, sometimes undoing a decision someone made for a good reason. GitHub's blog makes a similar case in Write Better Commits, Build Better Projects, which frames each message around intent, context, implementation and justification.
A template to copy:
type(scope): what the system does differently now
What broke: the symptom, as someone using the system saw it.
Why: the cause.
Verified: what was run, on what data, and the result
(before and after numbers if there are any).
Left alone: related code that was not changed, and why.
Refs: spec 037
Not every commit needs every section. A typo fix needs a subject. But any commit that changes behaviour, touches data or fixes a bug gets the full body.
Put the measured evidence in the commit
When a commit claims something got faster or stayed the same, the body carries the number. My commit bodies include lines like "117 ms to 1.5 ms" for an index, or "35/35 identical" for a rewrite that was checked by diffing old and new output. That second one comes from the approach in verifying a refactor by diffing output.
A number in the commit does two things. It tells a reviewer the change was measured, and it gives anyone who touches the code later a baseline. If that query is slow again a year from now, the commit says what fast looked like and how it was measured.
The other thing I put in the body is the reason a risky step is safe. For a data migration, a line like "every row is saved to a backup table before it changes and down() restores them" answers the reviewer's first question before they ask it. It also commits the author to having built the backup, which is a useful discipline in itself.
Reference the spec the work came from
My team works spec-first. Every non-trivial change has a numbered spec, and commits reference it, for example "(spec 037)". The spec holds the full reasoning: the problem, the options considered, the decision. The commit holds the slice of it this change delivers.
That link keeps commit bodies short without losing context. A reader who wants the whole story follows the number. I cover how we write and number specs in spec-driven development.
Who reads git history now: reviewers and AI coding agents
As a team lead, git history is one of my main review tools. When a PR changes code I did not write, git log and git blame on the touched lines tell me why that code is shaped the way it is. If the earlier commits explain themselves, I can review the new change against the original intent. If they say "fix stuff", I have to ask someone, or guess.
AI coding agents read git history and commit messages as context too. An agent asked to change a function can look at recent commits on that file to understand it. A message that says what broke, why and what was left alone gives the agent the same context it gives a human reviewer. A message that says "wip" gives it nothing, and the agent fills the gap with its own assumptions.
So the why matters more now than it used to. Commit messages have become part of the context you give AI coding agents, alongside the conventions and specs. I wrote about the conventions side in making team conventions machine-readable for AI agents, and about the wider team setup in rolling out AI coding assistants across a team.
How long should a commit message subject line be?
Git's own documentation for git commit suggests starting with a single short line of no more than 50 characters that summarises the change, followed by a blank line and the body. I treat 50 as a guide: a clear subject of 55 characters beats a cryptic one of 45. Keep it to one line that reads well in git log --oneline.
Where I'd start
Adopt the four-part body for any commit that changes behaviour: what broke, why, how it was verified, what was left alone. Put measured numbers in the body whenever there are any, and reference the spec. Then use the history yourself when reviewing, so the team sees it being read. If you want help setting up these habits on your own team, that is part of what I do through my services.