The simple version
A commit message that says fix null check in user loader tells you what happened. A commit message that says fix null check in user loader because the payments API returns null for guest users instead of an empty object, which we only discovered in prod tells you everything you actually need six months later.
What git history is actually for
Version control is sold as a backup system for code. That framing is technically correct and practically useless. You don’t need git to back up files. You need git because software is a record of decisions, and decisions require context to understand.
When you run git log on a production codebase that’s more than a year old, you’re not browsing a list of changes. You’re reading the archaeology of every tradeoff, every constraint, every “we’ll fix this properly later” that the team ever made. The question is whether that archaeology is readable or just a series of unmarked strata.
Most codebases look like this:
fix tests
fix tests again
actually fix tests
refactor
wip
wip 2
update user model
PR feedback
This is not version control. This is version control-shaped noise.
The difference between what and why
Every diff already tells you what changed. That’s the job of the diff. The file changed. These lines were added. Those were removed. Git will show you this forever, perfectly, without any help from you.
What the diff cannot tell you is why the code is the shape it is. Why is there a hardcoded timeout of 3200 milliseconds? Why does this function handle the empty list case before the null case, which is backwards from every other function in the file? Why is this feature flag still here when the feature shipped eight months ago?
The why lives only in the commit message, or in somebody’s head, or nowhere. And the people who were in the room when the decision was made are, eventually, not in the room anymore.
This isn’t a minor inconvenience. When a developer encounters code they don’t understand and can’t find any explanation for, they face a choice: change it and risk breaking something, or leave it and accumulate more unexplained weight. Most choose to leave it. This is how codebases become fragile and mysterious over time, not through malice, but through information loss.
What a good commit message actually looks like
There’s a widely-cited format that originates from Tim Pope’s 2008 blog post and was later formalized in various Git conventions. The structure is: a short subject line (under 72 characters), a blank line, and then a body that explains why the change was necessary, what the previous behavior was, and what constraints shaped the solution.
In practice, that looks something like this:
Skip email verification for OAuth signups
Users coming through Google OAuth already have a verified email per
Google's own policy. Running them through our email verification flow
causes ~12% of OAuth signups to drop off when they get the verification
email and assume it's spam.
We're skipping verification only when the `oauth_provider` field is
set on the incoming user record. This is set exclusively by the OAuth
callback handler. Direct signups are unaffected.
Tradeoff: if Google's verification policies ever change, we'd be
trusting unverified emails. Acceptable for now; document with a TODO.
Notice what’s there. The what (skipping email verification for OAuth users). The why (drop-off rate from a flow that was redundant). The scope (only when oauth_provider is set). The explicit tradeoff and its reasoning. A future developer reading this doesn’t need to reconstruct the original decision. It’s all here.
This took maybe four minutes to write. The alternative is that someone spends forty minutes later trying to figure out why this code path exists, probably fails to figure it out fully, and makes a slightly wrong assumption that costs them more time later.
The AI angle that most people are sleeping on
This is where things get genuinely interesting right now. LLMs are increasingly being used to help developers understand codebases, and the quality of commit history turns out to matter a lot for how well that works.
When you ask an AI coding assistant “why does this function have this weird edge case?” the model can look at the code, it can look at the diff, and if your commit messages contain real context, it can surface that context. If your commit history is fix stuff and wip, the model has nothing to work with and will hallucinate a plausible-sounding explanation that may be completely wrong.
Good commit messages are, in a practical sense, structured documentation that both humans and automated tools can query. They’re searchable. They’re parseable. They persist. The codebase’s git history is one of the few artifacts that actually tracks the evolution of reasoning, not just the evolution of the files.
There’s a related problem worth thinking about here: if you’re using AI-assisted tools to generate commit messages, you need to be intentional about prompting them for why, not just what. A model that summarizes a diff will tell you what changed. You have to explicitly ask it to capture the intent, the constraints, and the alternatives considered. That context usually has to come from you.
The objection everyone raises
“We don’t have time to write this for every commit.” This is true and also a false framing. You don’t need this for every commit. A commit that fixes a typo in a comment doesn’t need four paragraphs. A commit that introduces a new retry mechanism for a flaky third-party API absolutely does.
The discipline is developing the judgment to know which commits carry decisions that future-you (or a colleague, or an AI assistant, or someone who joins the team in two years) will need to reconstruct. Those commits are rarer than you think but more important than almost anything else you’ll write that day.
The commit message is the only place in the entire software development process where you are writing to someone who already has the code but doesn’t have the context. Every other form of documentation either precedes the code or describes its behavior. Only the commit message documents the moment of decision.
That’s worth four minutes.