Conventional commits describe code, not change

Echo ·

Conventional commits solved a real problem. Before the convention, working out whether a release was a patch or a minor meant asking around. Now the commit says so itself, and a tool can read it.

That is what the convention is for. It is a machine-readable hint about version numbers. Somewhere along the way it also became the input to the changelog, and the changelog has been paying for it since.

What the prefix actually encodes

The spec is small. feat: means a minor bump. fix: means a patch. BREAKING CHANGE: in the footer means a major. Run semantic-release and it reads the log, picks the number, and groups the subject lines under Features and Bug Fixes.

The output looks like this:

Bug Fixes

  • null check in session middleware (a1b3f9c)

Features

  • add retry to webhook dispatch (e4c2d10)
A records room lined with identical metal filing drawers, one drawer pulled open under cool overhead light
Sorted, numbered, and filed. Still nobody's answer to what changed.

A customer reads "null check in session middleware" and learns nothing. They do not know what a session middleware is, and they should not have to. What happened, from where they sit, is that the app stopped logging them out at random. No commit in that release says so, because no engineer had a reason to write it down. The fix was to the null check. The consequence was to the logout loop, and the consequence is the part worth publishing.

The prefix is about the code, by design

This is not a failure of discipline. Teams with immaculate commit hygiene hit the same wall.

feat: and fix: classify the change by its effect on the version number. That is a fact about the code. What a changelog needs is a fact about the product: who was affected, what they experienced, whether they need to do anything now. The convention was never built to carry that, and adding more prefixes does not help. feat(billing): still describes a diff.

There is a second gap underneath. One user-facing change often arrives as several commits across several repositories, some of them chore: or refactor:, none of them individually a feature. A generator working commit by commit sees the pieces and never the thing. That is the reconstruction problem, and it is why grouping by prefix produces a tidy list of parts.

A worn sorting counter at night under one lamp, a rack of rubber stamps beside a closed ink pad
Where the prefix ends and the writing starts.

Keep the convention, move the writing

None of this is an argument against conventional commits. Keep them. Automatic version bumps are worth having on their own, and a well-formed log is genuinely easier to read six months later.

The mistake is asking the log to do a second job it was never shaped for.

Echo reads the same commits, then keeps going. The diffs behind them, the pull requests they belong to, the issues they close, across every repository in the release. From that it works out what changed for someone who never saw the code, and writes it once per audience. The team gets the engineering account. Customers get the logout loop. Developers get the breaking changes and the migration path. Agents get it structured.

A rooftop at dawn, four radio antennas of different heights against a pale sky
The same release, four times, in four registers.

This is the same problem GitHub's generated release notes run into from the other direction. One reads pull request titles, the other reads commit prefixes. Both are reading what engineers wrote for each other and hoping it works for everyone else.


Your commit log is a good record of the code. It was never a record of the change.

All posts

© 2026 Echo