<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>changeloop blog</title><description>Release notes practice, and changelogs as a build artifact.</description><link>https://changeloop.dev/</link><item><title>Changelog automation, and its limits</title><link>https://changeloop.dev/blog/changelog-automation/</link><guid isPermaLink="true">https://changeloop.dev/blog/changelog-automation/</guid><description>Automate collection, formatting and publishing. Do not automate selection or wording. Here is where the line sits and what happens each time it moves.</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Changelog automation projects fail in one of two directions, and both are predictable from the
first design meeting.&lt;/p&gt;
&lt;p&gt;Automate too little and the changelog is a document someone is supposed to update, which means it
is updated in bursts, before releases, by whoever drew the short straw. Automate too much and it
becomes a formatted git log: complete, accurate, and read by nobody.&lt;/p&gt;
&lt;p&gt;The useful question is not how much to automate. It is which parts.&lt;/p&gt;
&lt;h2&gt;Four steps, and only three of them should be automatic&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Collection.&lt;/strong&gt; Getting changes out of the place they happen (commits, PRs, tickets) and into a
list. Automate this completely. Humans are bad at it, it is tedious, and it is the step that gets
skipped under deadline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Classification.&lt;/strong&gt; Deciding whether something is Added, Fixed, Changed, Deprecated, Removed or
Security. Automate the first pass from commit type or PR label, and let a human override. Accuracy
here is around eighty percent from metadata alone, and the wrong twenty percent is concentrated in
exactly the entries that matter, because ambiguity correlates with significance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Selection and wording.&lt;/strong&gt; Deciding what a reader should be told and how to say it. &lt;strong&gt;Do not
automate this.&lt;/strong&gt; It is the entire value of the artifact. Everything else is logistics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Publishing.&lt;/strong&gt; Getting the finished entries to a page, a feed, an email, an in-app widget, a Slack
channel. Automate completely, and from one source. This is where most of the manual effort actually
goes, and almost nobody counts it.&lt;/p&gt;
&lt;p&gt;That last point is worth sitting with. Teams tend to think of the changelog as a writing problem,
then spend most of their time on distribution: copying entries into an email tool, reformatting for
in-app, pasting into Slack, updating a docs page. The writing is an hour. The copying is an hour
every release, forever, and it is the part a machine should have.&lt;/p&gt;
&lt;h2&gt;What happens when the line moves&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Move it up and you get a git dump.&lt;/strong&gt; Full automation from commits produces &lt;code&gt;bump deps&lt;/code&gt;,
&lt;code&gt;fix flaky test&lt;/code&gt;, &lt;code&gt;wip&lt;/code&gt; and &lt;code&gt;address review comments&lt;/code&gt; in front of customers. Every team that has
done this has then added a filter, and the filter is a selection step reintroduced under another
name, with worse ergonomics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Move it down and you get bursts.&lt;/strong&gt; Fully manual collection means entries are written from memory
at release time. That is the mode Keep a Changelog opens by warning about, and it degrades quietly:
the changelog looks maintained right up until the week nobody had time.&lt;/p&gt;
&lt;h2&gt;A pipeline that holds&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;On merge, derive a draft entry from the PR: type from label or commit prefix, title as a first
draft, link back to the PR, author recorded. Land it in an unreleased bucket.&lt;/li&gt;
&lt;li&gt;Anyone can edit any draft at any time, and edits are cheap. Most get one line rewritten.&lt;/li&gt;
&lt;li&gt;Cutting a release requires every entry in the bucket to be either edited or explicitly marked
internal. This gate is the whole design. Without it, drafts ship unedited on the busy week.&lt;/li&gt;
&lt;li&gt;Publishing is a fan-out from the released set: the public page, the feed, the email, the widget,
the Slack post. One source, several renderings, no copying.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Step 3 is the only place a human is required, and it takes about ten minutes per release once the
drafts are decent.&lt;/p&gt;
&lt;h2&gt;What this requires of your data&lt;/h2&gt;
&lt;p&gt;None of the above works if the changelog is a Markdown file, because a file cannot be rendered into
five surfaces without parsing it back out, and parsing prose is how you end up with a widget that
displays half a heading.&lt;/p&gt;
&lt;p&gt;Entries need to be structured: a type, a date, a version or release identifier, an audience, a body
and a link. Then the file, the page, the feed and the email are all views. That structural point is
the one thing worth getting right before you pick a tool, because it is the thing you cannot retrofit
cheaply.&lt;/p&gt;
&lt;p&gt;We build changeloop, where the changelog is a feed first and a page second, so treat that as an
interest rather than an impartial recommendation. &lt;a href=&quot;https://changeloop.dev/changelog-tools&quot;&gt;Changelog tools&lt;/a&gt; is our roundup of what else
is out there, including the products we compete with, and the
&lt;a href=&quot;https://changeloop.dev/changelog-generator&quot;&gt;changelog generator&lt;/a&gt; does the collection and classification steps in the
browser if you want to see the shape before committing to a pipeline.&lt;/p&gt;
&lt;h2&gt;The test&lt;/h2&gt;
&lt;p&gt;Count the minutes between a change being merged and that change being visible to a customer who
does not read your repo. If most of those minutes are someone copying text between tools, the
automation you need is in publishing, not in writing.&lt;/p&gt;
</content:encoded></item><item><title>Changelog vs release notes: what is the difference?</title><link>https://changeloop.dev/blog/changelog-vs-release-notes/</link><guid isPermaLink="true">https://changeloop.dev/blog/changelog-vs-release-notes/</guid><description>A changelog is a running record for people looking something up. Release notes are a curated message for people deciding whether to care. Here is the split.</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most teams end up with one of these by accident and the other by request. You start with a
changelog because a developer wants a record of what shipped. Months later someone in support asks
why customers did not know about a feature that has been live since April, and now you need release
notes.&lt;/p&gt;
&lt;p&gt;They are not the same artifact, and the difference is not formatting. It is audience.&lt;/p&gt;
&lt;h2&gt;The short version&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;changelog&lt;/strong&gt; is a running, cumulative record of what changed. It is chronological, it is close
to complete, and its reader has already decided they care. They are looking something up: when a
behaviour changed, whether a bug is fixed, which version introduced a flag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Release notes&lt;/strong&gt; are a curated message about one release. They are selective, written in prose,
and their reader has not decided anything yet. They are deciding whether this release matters to
them, and whether they have to do anything about it.&lt;/p&gt;
&lt;p&gt;One is a reference. The other is an announcement.&lt;/p&gt;
&lt;h2&gt;Side by side&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Changelog&lt;/th&gt;
&lt;th&gt;Release notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Reader&lt;/td&gt;
&lt;td&gt;Someone looking something up&lt;/td&gt;
&lt;td&gt;Someone deciding whether to care&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;Everything that changed&lt;/td&gt;
&lt;td&gt;What is worth saying about this release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cadence&lt;/td&gt;
&lt;td&gt;Continuous, per merge or per release&lt;/td&gt;
&lt;td&gt;Per release, and only releases worth announcing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tone&lt;/td&gt;
&lt;td&gt;Terse, factual, often imperative&lt;/td&gt;
&lt;td&gt;Explanatory, sometimes persuasive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifespan&lt;/td&gt;
&lt;td&gt;Permanent, and read years later&lt;/td&gt;
&lt;td&gt;Read in the first week, then archived&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lives in&lt;/td&gt;
&lt;td&gt;The repo, a docs site, a &lt;code&gt;/changelog&lt;/code&gt; page&lt;/td&gt;
&lt;td&gt;Email, in-app, a blog post, a release page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fails by&lt;/td&gt;
&lt;td&gt;Being incomplete&lt;/td&gt;
&lt;td&gt;Being boring, or arriving after the fact&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;Why the confusion is reasonable&lt;/h2&gt;
&lt;p&gt;Small teams publish one artifact that does both jobs, and for a while that is correct. A single
&lt;code&gt;/changelog&lt;/code&gt; page with a short paragraph at the top of each entry will serve a developer looking up
a fix and a customer skimming for news. Splitting too early gives you two things to maintain and
one of them will rot.&lt;/p&gt;
&lt;p&gt;The split becomes worth it when the two audiences start wanting different things. Some signs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your changelog entries have grown explanatory paragraphs that developers scroll past.&lt;/li&gt;
&lt;li&gt;Or the opposite: your release announcements have started listing dependency bumps.&lt;/li&gt;
&lt;li&gt;Support is copying entries into emails and rewriting them on the way.&lt;/li&gt;
&lt;li&gt;Someone asks for &amp;quot;just the breaking changes&amp;quot; and you cannot filter for them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last one is the real tell. If nobody can answer &amp;quot;what changed that affects me&amp;quot; without reading
everything, you have one artifact doing two jobs badly.&lt;/p&gt;
&lt;h2&gt;The version that works: one source, two views&lt;/h2&gt;
&lt;p&gt;The mistake is to treat them as two documents. They are two views over the same set of changes.&lt;/p&gt;
&lt;p&gt;Write the changelog as you go, one entry per meaningful change, each tagged with what it is: fixed,
added, changed, removed, deprecated, security. Keep the entries short enough that writing one is
not a decision. Then, at release time, release notes are a selection and a rewrite: take the
entries that matter to a human, group them by what they let someone do, and put the reason at the
top.&lt;/p&gt;
&lt;p&gt;This has a practical consequence. If the changelog is the source, it needs to be structured data,
not a hand-maintained page. An entry needs a type, a date, a version, and a way of saying who it is
for. Once it has that, the release notes email, the in-app widget and the public page are three
renderings of one thing, and nobody rewrites anything on the way to a customer.&lt;/p&gt;
&lt;p&gt;That is the whole argument for treating a changelog as a feed rather than a page. It is also,
transparently, what we build, so read that as an interest rather than a neutral survey.&lt;/p&gt;
&lt;h2&gt;If you only have time for one&lt;/h2&gt;
&lt;p&gt;Write the changelog. It is cheaper per entry, it is useful the day you write it, and release notes
can be derived from it later. The reverse is not true: you cannot reconstruct a year of changes
from twelve announcement emails, and people will ask you to.&lt;/p&gt;
&lt;p&gt;Keep it in a fixed format so the derivation stays possible. Our
&lt;a href=&quot;https://changeloop.dev/changelog-examples&quot;&gt;changelog examples&lt;/a&gt; page collects entries from teams that do this well, and
the &lt;a href=&quot;https://changeloop.dev/release-notes-template&quot;&gt;release notes template&lt;/a&gt; is the shape we use when turning a set of
entries into something worth sending.&lt;/p&gt;
&lt;h2&gt;A note on naming&lt;/h2&gt;
&lt;p&gt;None of this is standardised, and you will find &amp;quot;release notes&amp;quot; used for a running list and
&amp;quot;changelog&amp;quot; used for a quarterly announcement. Arguing about the words is not worth it. Decide
which of the two jobs each of your artifacts is doing, name it whatever your team already calls it,
and make sure neither is quietly doing both.&lt;/p&gt;
</content:encoded></item><item><title>How to write release notes people actually read</title><link>https://changeloop.dev/blog/how-to-write-release-notes/</link><guid isPermaLink="true">https://changeloop.dev/blog/how-to-write-release-notes/</guid><description>Bug fixes and performance improvements is not a release note. Here is the question every entry has to answer, and a before and after rewrite of a real one.</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Bug fixes and performance improvements.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Every product has shipped this. It is not lazy writing exactly. It is what you get when release
notes are written from the inside, by someone who has spent two weeks in the diff and can no longer
see which parts of it a stranger would care about.&lt;/p&gt;
&lt;p&gt;The fix is not a better tone of voice. It is answering one question per entry.&lt;/p&gt;
&lt;h2&gt;The question&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What can the reader do now that they could not do before, and what do they have to do about it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If an entry cannot answer that, it belongs in the changelog and not in the release notes. Both
halves matter. The first half is the value. The second half is the part teams forget, and it is the
one that generates support tickets when it is missing.&lt;/p&gt;
&lt;p&gt;Two examples of the second half doing real work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;Existing webhooks keep working until 1 November. After that, unsigned payloads are rejected.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;No action needed. Existing exports are re-encoded automatically the next time you open them.&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second one says &amp;quot;no action needed&amp;quot; explicitly. That sentence is worth writing every single
time, because a reader who cannot find it assumes the worst.&lt;/p&gt;
&lt;h2&gt;Order by consequence, not by component&lt;/h2&gt;
&lt;p&gt;The most common structural mistake is grouping by where the work happened: API, dashboard, mobile,
infrastructure. That is your org chart, not the reader&amp;#39;s problem.&lt;/p&gt;
&lt;p&gt;Order it like this instead:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Breaking changes and anything with a deadline.&lt;/strong&gt; First, always, even if it is small. If a
reader stops after one line, this is the line they must have read.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What is new that they will want.&lt;/strong&gt; One per paragraph, with the outcome in the first clause.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What got better.&lt;/strong&gt; Fixes that were reported, limits that were raised, things that were slow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Everything else, as a list.&lt;/strong&gt; Dependency bumps, internal refactors, minor copy. One line each.
Nobody reads this section, and it should still be there, because the person looking for it
really needs it.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;The rewrite&lt;/h2&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;v4.2.0&lt;/strong&gt; Fixed an issue where the &lt;code&gt;POST /exports&lt;/code&gt; endpoint would intermittently return 500
under load. Refactored the export worker. Bumped &lt;code&gt;node-pg&lt;/code&gt; to 8.11. Improved error handling in
the CSV serializer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Exports no longer fail on large accounts.&lt;/strong&gt;
Accounts over roughly 50,000 rows could get a 500 when starting an export, more often at
month end. That is fixed, and exports of any size now retry themselves rather than failing.
No action needed, and any export that failed in the last week can simply be run again.&lt;/p&gt;
&lt;p&gt;Also in 4.2.0: &lt;code&gt;node-pg&lt;/code&gt; 8.11, clearer CSV serializer errors.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Same release. The second one names the account that was affected, the time it was worst, what
changed, and what to do. The dependency bump did not disappear, it just stopped being the headline.&lt;/p&gt;
&lt;h2&gt;Things worth deleting&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&amp;quot;We are excited to announce.&amp;quot;&lt;/strong&gt; The reader is not excited yet. Earn it in the sentence after.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Internal ticket numbers.&lt;/strong&gt; &lt;code&gt;PROJ-4471&lt;/code&gt; means nothing outside your tracker. If the entry needs a
reference, link the docs page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Component names only your team uses.&lt;/strong&gt; If you renamed the &amp;quot;ingest pipeline&amp;quot;, say &amp;quot;imports&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A version number as the only headline.&lt;/strong&gt; &lt;code&gt;v4.2.0&lt;/code&gt; is a filing label, not a summary.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Screenshots of a settings page nobody has visited.&lt;/strong&gt; Show the thing that changed, in use.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cadence beats volume&lt;/h2&gt;
&lt;p&gt;Notes that arrive for every release train everyone to ignore them. Notes that arrive when something
happened get opened. That means it is fine, and usually correct, to ship a release with no notes at
all and roll its entries into the next set that has a headline worth reading.&lt;/p&gt;
&lt;p&gt;The changelog still records all of it. That is the division of labour: the changelog is complete,
the notes are selective. If you keep the changelog structured as you go, writing the notes is
selection and rewriting rather than archaeology.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://changeloop.dev/release-notes-template&quot;&gt;release notes template&lt;/a&gt; is the shape we use for the selection step,
and &lt;a href=&quot;https://changeloop.dev/changelog-examples&quot;&gt;changelog examples&lt;/a&gt; collects entries from teams whose changelog is good
enough to derive notes from.&lt;/p&gt;
&lt;h2&gt;One test before you publish&lt;/h2&gt;
&lt;p&gt;Read the notes as someone who has been on holiday for two weeks and has 40 seconds. If, in that
time, they cannot tell whether anything is required of them, the notes are not finished, however
accurate they are.&lt;/p&gt;
</content:encoded></item><item><title>Release notes best practices worth keeping</title><link>https://changeloop.dev/blog/release-notes-best-practices/</link><guid isPermaLink="true">https://changeloop.dev/blog/release-notes-best-practices/</guid><description>Most best practice lists for release notes are style advice. These are the ones that change what a reader does, and three popular ones that are cargo cult.</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Search for release notes best practices and you get style advice: be clear, be concise, use plain
language, add screenshots. None of it is wrong and none of it changes anything, because no team has
ever sat down intending to be unclear.&lt;/p&gt;
&lt;p&gt;The practices below are the ones with a consequence. Each is followed by what it costs you if you
skip it, because a practice without a failure mode attached is just a preference.&lt;/p&gt;
&lt;h2&gt;Practices that earn their place&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Write the entry when you merge, not when you release.&lt;/strong&gt;
Cost of skipping: the person reconstructing the release from commit history is not the person who
made the change, and they will guess at intent. Entries written a fortnight later are the ones that
say &amp;quot;various improvements&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Say who is affected, by name.&lt;/strong&gt;
&amp;quot;Teams on the Business plan&amp;quot;, &amp;quot;anyone using the v1 export API&amp;quot;, &amp;quot;self-hosted installs on Postgres
14&amp;quot;. Cost of skipping: every reader has to work out whether it applies to them, and most will
decide it does not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;State the required action, including when it is none.&lt;/strong&gt;
Cost of skipping: support answers the same question forty times, and the readers who did not ask
just assume something is required and put it off.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Give breaking changes a date, not a release number.&lt;/strong&gt;
&amp;quot;Removed in v5&amp;quot; means nothing to someone who does not know when v5 lands. &amp;quot;Stops working on
1 November&amp;quot; is a date they can put in a calendar. Cost of skipping: the deadline is discovered
after it passes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep one permanent, linkable entry per change.&lt;/strong&gt;
An email is not an archive and a Slack message is not a reference. Cost of skipping: nobody can
answer &amp;quot;when did this change&amp;quot; six months later, including you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Group by outcome, not by system.&lt;/strong&gt;
Cost of skipping: the reader has to hold your architecture in their head to work out which section
matters to them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep the boring section.&lt;/strong&gt;
Dependency bumps and internal changes stay, at the bottom, in one line each. Cost of skipping: the
security team, the compliance reviewer and the person debugging a version mismatch all lose their
only source.&lt;/p&gt;
&lt;h2&gt;Changelog best practices, which are not the same thing&lt;/h2&gt;
&lt;p&gt;A changelog is a reference, so its practices are about completeness and structure rather than
persuasion:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A fixed entry type per line.&lt;/strong&gt; Added, Changed, Deprecated, Removed, Fixed, Security. Not a
house style, a filter: it is what lets someone ask for &amp;quot;just the breaking changes&amp;quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An unreleased section.&lt;/strong&gt; Where entries live between merge and release. Its absence is why teams
write entries late.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISO dates.&lt;/strong&gt; &lt;code&gt;2026-08-28&lt;/code&gt;, not &lt;code&gt;28/08/26&lt;/code&gt;, which means two different days depending on the
reader.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One entry per change, not per commit.&lt;/strong&gt; Three commits fixing one bug are one entry.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Three that are cargo cult&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Emoji as entry types.&lt;/strong&gt; A rocket and a wrench are not a taxonomy. They look tidy and they cannot
be filtered, sorted, or read by a screen reader in any useful way. Use words, and if you want the
emoji, put them after the word.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Semantic version numbers as headlines for a hosted product.&lt;/strong&gt; Semver is a promise about API
compatibility. For a SaaS product where nobody chooses their version, a version number in the
headline is internal filing dressed up as news. Keep semver in the changelog and out of the
announcement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Publishing on a schedule regardless of content.&lt;/strong&gt; Monthly notes with nothing in them teach people
that your notes are noise. Publish when there is something to say. The changelog covers the rest.&lt;/p&gt;
&lt;h2&gt;The one that is actually hard&lt;/h2&gt;
&lt;p&gt;Keeping the changelog and the announcement in step, without writing everything twice.&lt;/p&gt;
&lt;p&gt;Most teams start with one page, split it when the audiences diverge, and then quietly let one of
the two rot, usually the changelog, because it is the one with no deadline attached. The way out is
structural rather than disciplinary: keep the entries as data with a type, a date and an audience,
and treat both surfaces as renderings of that. Our &lt;a href=&quot;https://changeloop.dev/changelog-tools&quot;&gt;changelog tools&lt;/a&gt; roundup
covers what is available for that, including the tools we compete with.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://changeloop.dev/release-notes-template&quot;&gt;release notes template&lt;/a&gt; is where the selection step lives once the
entries exist.&lt;/p&gt;
&lt;h2&gt;If you adopt one thing&lt;/h2&gt;
&lt;p&gt;Write the entry at merge time, in a fixed format, with a type. Every other practice on this page
gets easier once that one is in place, and none of them survive without it.&lt;/p&gt;
</content:encoded></item><item><title>Keep a Changelog, actually implemented</title><link>https://changeloop.dev/blog/keep-a-changelog-implemented/</link><guid isPermaLink="true">https://changeloop.dev/blog/keep-a-changelog-implemented/</guid><description>The spec is one page and takes ten minutes to read. Implementing it is where teams drift. Here is what it says, what it leaves open, and where it goes wrong.</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Olivier Lacan published Keep a Changelog in 2014 with a line that has aged better than most
software writing: &lt;em&gt;don&amp;#39;t let your friends dump git logs into changelogs&lt;/em&gt;. Ten years on it is the
closest thing this corner of software has to a standard, which mostly means people cite it and then
implement about two thirds of it.&lt;/p&gt;
&lt;p&gt;It is worth reading the source rather than a summary of it. This is about the third that gets
dropped.&lt;/p&gt;
&lt;h2&gt;What the spec actually asks for&lt;/h2&gt;
&lt;p&gt;A &lt;code&gt;CHANGELOG.md&lt;/code&gt; at the repo root, newest first, with one section per version. Each version carries
a number and an ISO date, and groups its entries under six types:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Added&lt;/td&gt;
&lt;td&gt;new features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changed&lt;/td&gt;
&lt;td&gt;changes in existing behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deprecated&lt;/td&gt;
&lt;td&gt;features about to be removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Removed&lt;/td&gt;
&lt;td&gt;features removed in this release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed&lt;/td&gt;
&lt;td&gt;bug fixes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;vulnerabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Plus an &lt;code&gt;Unreleased&lt;/code&gt; section at the top, so there is somewhere to put an entry the moment it is
merged, and so anyone can see what is coming.&lt;/p&gt;
&lt;p&gt;That is nearly all of it. The rest is the reasoning: entries are for humans, one entry per change,
and the file is a document rather than a log.&lt;/p&gt;
&lt;h2&gt;The three parts that get dropped&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;Unreleased&lt;/code&gt; disappears first.&lt;/strong&gt; It is the section with no deadline, so it is the one that stops
being maintained, and once it is gone entries get written at release time from commit history. That
is precisely the git-log dump the spec opens by warning about, arrived at gradually.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The six types collapse into two.&lt;/strong&gt; Most real changelogs end up with Added and Fixed, because
Changed and Deprecated require a judgement about what someone relied on. That judgement is the
valuable part. Deprecated in particular is the only type that is a promise about the future, and
dropping it is how a removal turns into an incident.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security stops being separate.&lt;/strong&gt; A security fix filed under Fixed is invisible to the one reader
who was scanning for it. Keep it distinct even when the fix is trivial, and especially when you
would rather not draw attention to it.&lt;/p&gt;
&lt;h2&gt;What the spec deliberately does not answer&lt;/h2&gt;
&lt;p&gt;It is a file format. It says nothing about the questions you hit immediately after adopting it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;How does anyone find out?&lt;/strong&gt; A file in a repo reaches contributors. It does not reach a customer
who has never opened GitHub.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What about products without versions?&lt;/strong&gt; A continuously deployed service has no v4.2.0 to group
by. Most teams substitute dates, which works, and the spec does not bless or forbid it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Who writes the entry?&lt;/strong&gt; The spec assumes a human does. It does not say when.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What about multiple audiences?&lt;/strong&gt; One file serves developers. It does not serve the same content
to a non-technical admin, and reformatting it by hand for them is where the duplication starts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Common Changelog, a stricter fork of the idea, tightens some of this: it bans certain entry
phrasings, requires a link to the change, and is opinionated about who the reader is. It is worth
reading if the loose parts of Keep a Changelog are what your team keeps arguing about.&lt;/p&gt;
&lt;h2&gt;Automating it without dumping git logs&lt;/h2&gt;
&lt;p&gt;The spec&amp;#39;s warning is about output, not about tooling. Deriving a draft from structured commits is
fine. Publishing that draft unedited is the thing it objects to.&lt;/p&gt;
&lt;p&gt;A shape that holds up: derive candidate entries from commits automatically, put them in
&lt;code&gt;Unreleased&lt;/code&gt; with their type prefilled, and require a human to edit the wording before a release
can be cut. The machine handles collection and formatting, which it is good at. The human handles
selection and phrasing, which it is not. Our &lt;a href=&quot;https://changeloop.dev/changelog-tools&quot;&gt;changelog tools&lt;/a&gt; roundup covers
what exists for the collection half.&lt;/p&gt;
&lt;h2&gt;Where it stops being enough&lt;/h2&gt;
&lt;p&gt;Keep a Changelog is a good answer to &amp;quot;what should this file look like&amp;quot;. It is not an answer to
&amp;quot;how do our users learn what changed&amp;quot;, because a Markdown file in a repo is a distribution strategy
that only works if your users are contributors.&lt;/p&gt;
&lt;p&gt;That is the gap most teams hit second: the file is fine, and nobody outside the team reads it.
Solving it means the entries have to become data that can be rendered somewhere else, which is a
different problem from formatting a file, and the reason &lt;a href=&quot;https://changeloop.dev/changelog-examples&quot;&gt;changelog examples&lt;/a&gt;
collects public changelog pages rather than repo files.&lt;/p&gt;
&lt;p&gt;Adopt the spec anyway. It costs an afternoon, it makes the second problem tractable, and it is
still the best one page written about this.&lt;/p&gt;
</content:encoded></item><item><title>From conventional commits to a changelog</title><link>https://changeloop.dev/blog/conventional-commits-changelog/</link><guid isPermaLink="true">https://changeloop.dev/blog/conventional-commits-changelog/</guid><description>Conventional commits make a changelog derivable. They do not make it readable. Here is what the convention buys, where it stops, and how to bridge the gap.</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;feat(exports): add CSV column selection
fix(auth): reject expired refresh tokens
chore(deps): bump node-pg to 8.11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three commits in the Conventional Commits format. From these, a machine can tell you that one is a
feature, one is a fix, one is housekeeping, and which part of the system each touched. That is
genuinely useful, and it is the entire promise of the convention: a commit history that can be
read by something other than a person.&lt;/p&gt;
&lt;p&gt;The mistake is thinking that gets you a changelog. It gets you the raw material.&lt;/p&gt;
&lt;h2&gt;What the convention actually specifies&lt;/h2&gt;
&lt;p&gt;A type, an optional scope, and a description: &lt;code&gt;type(scope): description&lt;/code&gt;. Types are conventionally
&lt;code&gt;feat&lt;/code&gt;, &lt;code&gt;fix&lt;/code&gt;, &lt;code&gt;chore&lt;/code&gt;, &lt;code&gt;docs&lt;/code&gt;, &lt;code&gt;refactor&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;perf&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;ci&lt;/code&gt;. Two things mark a
breaking change: a &lt;code&gt;!&lt;/code&gt; before the colon, or a &lt;code&gt;BREAKING CHANGE:&lt;/code&gt; footer. Tooling keys off
&lt;code&gt;feat&lt;/code&gt; and &lt;code&gt;fix&lt;/code&gt; for minor and patch version bumps, and off the breaking marker for a major.&lt;/p&gt;
&lt;p&gt;It is a small spec and it is worth following even if you never generate anything from it, because
it forces one decision per commit: is this a change users see, or not.&lt;/p&gt;
&lt;h2&gt;Where it stops&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Commit messages are written for reviewers.&lt;/strong&gt; &lt;code&gt;fix(auth): reject expired refresh tokens&lt;/code&gt; is
correct and tells a customer nothing. The reader of a changelog wants &amp;quot;you will be signed out when
a session really has expired, instead of seeing intermittent 401s&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scopes are internal.&lt;/strong&gt; &lt;code&gt;exports&lt;/code&gt;, &lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;ingest&lt;/code&gt; are module names. They are stable, which makes
them good for grouping, and meaningless to anyone outside the codebase.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One change is often several commits.&lt;/strong&gt; A feature merged over eleven commits produces eleven
entries, ten of which are noise, and squashing to hide that loses the review history.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;chore&lt;/code&gt; is a bin, not a category.&lt;/strong&gt; Dependency bumps, CI changes and renames all land there, and
some of them matter to users while most do not.&lt;/p&gt;
&lt;p&gt;So: the convention gives you type, scope and breaking status for free, and leaves wording, grouping
and selection entirely open. Those three are the changelog.&lt;/p&gt;
&lt;h2&gt;The two-layer shape that works&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Layer one, automatic.&lt;/strong&gt; On merge, derive a draft entry from the commit: type mapped to a
changelog type (&lt;code&gt;feat&lt;/code&gt; to Added, &lt;code&gt;fix&lt;/code&gt; to Fixed, a breaking marker to Changed plus a flag), scope
kept as metadata rather than as text, link to the PR.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer two, human, and required.&lt;/strong&gt; Before a release goes out, every draft entry either gets a
one-line rewrite in the user&amp;#39;s vocabulary, or gets marked internal and dropped from the public
view. This is the step people try to skip, and skipping it is what produces changelogs that read
like a diff.&lt;/p&gt;
&lt;p&gt;The important design detail is that layer two is not optional in the pipeline. If a release can be
cut with unedited drafts, it will be, on the week when everyone is busy.&lt;/p&gt;
&lt;h2&gt;Three traps&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Squash merges eat the footers.&lt;/strong&gt; If your platform squashes with the PR title as the message, the
&lt;code&gt;BREAKING CHANGE:&lt;/code&gt; footer from a commit inside that branch disappears, and your tooling silently
stops seeing the breaking change. Check what your squash template actually keeps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Revert commits produce phantom entries.&lt;/strong&gt; A &lt;code&gt;fix&lt;/code&gt; that is reverted the next day generates an
entry for something that never shipped, unless the derivation reconciles reverts. Most tools do
not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Version bump and changelog get out of order.&lt;/strong&gt; If the version is computed from commits and the
changelog is written by hand afterwards, they drift within about two releases. Compute both from
the same pass or accept that one of them is wrong.&lt;/p&gt;
&lt;h2&gt;If you want the mechanical part without a pipeline&lt;/h2&gt;
&lt;p&gt;Our &lt;a href=&quot;https://changeloop.dev/changelog-generator&quot;&gt;changelog generator&lt;/a&gt; does the derivation step in the browser: paste
commits, get grouped, typed entries out. It is deliberately deterministic and entirely client side,
so the commits you paste never leave your machine, which matters when the messages are from a
private repo. It does the collection half honestly and makes no attempt at layer two, because
layer two is a judgement call and a tool that fakes it produces exactly the changelog this article
is arguing against.&lt;/p&gt;
&lt;p&gt;For the pipeline version, &lt;a href=&quot;https://changeloop.dev/changelog-tools&quot;&gt;changelog tools&lt;/a&gt; has the landscape.&lt;/p&gt;
&lt;h2&gt;The summary&lt;/h2&gt;
&lt;p&gt;Conventional commits answer &amp;quot;what kind of change is this&amp;quot; reliably and cheaply. They do not answer
&amp;quot;what should we tell people&amp;quot;, and no amount of tooling on top of the commit message will, because
the information was never in the commit message. Budget for the rewrite.&lt;/p&gt;
</content:encoded></item></channel></rss>