Software & TechnicalFree
Release Notes Generated From Commits
Name your commit range and who reads the notes, and get release notes grouped by what changed for them, breaking changes listed first.
River's release notes tool reads the range you name: the commits, the merged pull requests, the linked tickets and the diffs behind them. Out comes a set of release notes grouped by the user-visible change rather than by commit type, with breaking changes, removals and required migration steps at the top where they belong. Underneath sits a change register mapping every commit in the range to the user-facing statement it contributed to, so nothing in the range is silently dropped.
Page one for this query is generators, and they are all excellent at the job they do: sort the commits by prefix, print the subject lines, tag the version. That is a changelog for the people who wrote it. The mapping from a commit to what a customer will notice does not exist in the commit, so no amount of parsing recovers it. Neither does the answer to the only question a customer actually has, which is whether this release will break their upgrade.
Built for the engineer who ships on Thursday and writes the notes on Thursday afternoon, the founder whose changelog is the product's only marketing surface, and the platform team whose customers plan upgrades around a breaking-change list. Reach for it at the end of every release. Where the range covers a repository you did not write, the inherited codebase documentation is the better first pass, and where it broke an interface, the published OpenAPI specification needs the same edit. A break you chose deliberately belongs in an architecture decision record.
Seven of the twelve commit types are hidden by default
Keep a Changelog is blunt about the shortcut: using commit log diffs as changelogs is a bad idea because they are full of noise. The reason it gives is structural rather than stylistic. A commit documents a step in the evolution of the source code, while a changelog entry documents the noteworthy difference, often across multiple commits, written for end users. One entry to many commits is the normal ratio, and no generator that emits one line per commit can produce it.
The reference generator's own source settles the rest. Its default commit types ship twelve entries, and seven of them carry the value hidden: docs, style, chore, refactor, test, build and ci. A behaviour change committed as a refactor produces no line. A dependency bump committed as a chore that moved a library across a major version produces no line either. The neighbouring header pattern needs a type before the colon, so a commit written as a sentence has no type and lands nowhere.
Breaking changes are worse, because the specification puts them everywhere. Conventional Commits states that a breaking change can be part of commits of any type, so you cannot find them by reading the features and the fixes. Every commit's footer has to be read. Keep a Changelog closes the loop on the cost. A changelog which mentions only some of the changes is as dangerous as having none, because your users take it as the single source of truth and plan the upgrade from it.
How it works
Name the range
Give the repository and the two points you are releasing between, plus who reads the notes.
River reads everything
Commits, merged pull requests, linked tickets and diffs, including the types a generator would hide.
Read the notes
Grouped by user-visible change, breaking changes first, with the register showing every commit's destination.
Publish and move
Adjust the wording in chat, then ship the notes wherever your release announcement lives.
What you get
- Release notes grouped by the user-visible change, not by commit prefix or type
- Breaking changes, removals and required migration steps pulled to the top of the notes
- A change register mapping every commit in the range to the statement it fed
- The commits your generator hides read anyway, so a refactor that changed behaviour surfaces
- Non-conforming commit messages handled from the diff instead of dropped for lacking a prefix
- Written at the reading level of whoever you named, from developers to end users
Common questions
We already use conventional commits and a generator. What does this add?
The half of the range your generator drops. Seven of its twelve default types are hidden, so every behaviour change committed as a refactor or a dependency bump is missing from the output. It reads those, reads the diffs behind non-conforming messages, and then groups whatever is left by what a customer notices rather than by prefix.
Our commit messages are terrible. Will this work?
Yes, because the diff is the real source and the message is a hint. A commit called wip that adds a required field to a request body is a breaking change regardless of what it is called. Where a message and its diff disagree, the diff wins and the register records both, which is also a quietly useful review of your commit hygiene.
How does it decide what counts as breaking?
By what a caller has to change, not by the version number you were planning. A removed field, a narrowed type, a new required parameter, a different timezone in a response, a migration that must run first. Each one arrives with the commit that caused it, so you can argue with the classification. Where a migration renamed a column, the schema register says what it now holds.
What about commits nobody should see in the notes?
They go in the register with the reason they were excluded, never dropped in silence. Internal tooling, test scaffolding, a feature shipped and reverted inside the same range, and a refactor with no observable effect all end up there. You can promote any of them into the notes in chat, and the count always reconciles against the range.
Can it write for a non-technical audience?
That is the point of asking who reads them. Enterprise customers planning an upgrade get the breaking-change list and the migration order. End users on a self-serve product get what is new and what stopped annoying them. The same range produces different notes, and the register underneath is identical either way. The reviewer is a third audience and a different document, which the change summary writes.
Does it pick the version number?
It tells you what the change set implies and leaves the call to you. A removed field means a major whatever the roadmap says, and eleven fixes with no interface change means a patch. Where your commit metadata implies one number and the diffs imply another, the disagreement is reported rather than resolved quietly in your favour.
What if the range covers six months and four hundred commits?
Then grouping matters more, not less. Four hundred commits rarely exceed twenty user-visible changes, and the work is deciding which twenty. Long ranges also surface features that shipped and were reverted, and fixes that undid earlier fixes, which read as noise in a commit dump and as nothing in decent notes. Anywhere the range touched a documented endpoint, check what the spec no longer says first.
Release Notes Generated From Commits
Fill in the form and your workspace opens with the work already underway.