Changelog
A changelog is a curated, human-readable record of the notable changes in each version of software, written for the person deciding whether to upgrade. That reader is why a dump of commit messages, however complete, is not one. The page opens with a finished changelog, then covers the documents people confuse with it, what a version number promises, how to write an entry, deprecation, how far automation honestly goes, four releases where the changelog line was the problem, and where the practice degrades.
- Written for
- Whoever decides to upgrade
- Format
- Keep a Changelog 1.1.0
- Version scheme
- SemVer 2.0.0, since 2013
A changelog on one screen — finished
Most people who look up changelogs want to see a good one. Here is one, for a small library inside the online shop used across the Decomposition and Release Process pages. orders-client is the package through which four teams read orders: the web shop, the support console, the order confirmation emails and the nightly analytics export. Each team decides for itself when to upgrade, and that is exactly the reader a changelog is for. Only part of the file fits on a screen: the header, the Unreleased section and four versions. Later sections cite the lines by number.
1.Header — what the file is and what it promises
- All notable changes to
orders-clientare recorded here, newest first. The format follows Keep a Changelog 1.1.0 - Versions follow Semantic Versioning 2.0.0. The public APIApplication Programming Interface — contract between programsThe rules one program follows to call another: which requests it may send, in what shape, and what comes back. To the caller it is a contract — everything behind it stays a black box, which is exactly what lets the other side be rewritten without breaking you.Full article →All terms is what
docs/api.mdlists: exported functions, the fields of returned objects and error codes. Anything else may change in any release - Deprecation policy: anything deprecated keeps working until the next major version, and for at least six months after the release that deprecated it
- All notable changes to
2.[Unreleased]
- Added —
order.reorderSource:history,saved-basketornull, saying how a repeat order was started. Orders placed before 20 October always havenull - Deprecated —
getOrders({ customerId }). UsegetCustomerOrders(customerId)from 3.1. Removed in 3.0.0, and no sooner than six months after this release. Calling it logs one warning per process, and the endpoint behind it answers withDeprecationandSunsetheaders
- Added —
3.[2.7.0] — 2026-09-15
- Added —
getCustomerOrders(customerId), which pages through a history of any length.getOrdersstopped at 100 orders without saying so - Added —
address.postcode, holding the same value asaddress.zip - Deprecated —
address.zip. Readaddress.postcodeinstead.zipstays until 3.0.0, and at least until 15 March 2027; the first read in a process logs a warning - Security — the customer’s email address is no longer written to the debug log (
DEBUG=orders-client). Upgrade now if debug logging was ever switched on in production
- Added —
4.[2.6.2] — 2026-08-27
- Fixed —
order.placedAtis in UTCCoordinated Universal Time — the world’s reference timeThe time standard that clocks and logs are normally kept in, with no time zones and no daylight saving. It inserts leap seconds to stay close to the Earth’s rotation, which is why GPS time, which does not, now runs 18 seconds ahead of it.All terms again, as documented. 2.6.1 returned the server’s local time
- Fixed —
5.[2.6.1] — 2026-08-26 [YANKED]
- Fixed —
order.totalnow includes the voucher discount. Do not use this version: it also switchedplacedAtto local time, which put 212 late-evening orders on the wrong day in the nightly export. Pulled the next morning; use 2.6.2
- Fixed —
6.[2.0.0] — 2026-03-02
- Removed —
getOrderLines(orderId). The lines are now inorder.lines. See “Moving to 2.0”, step 1 - Changed — money is a whole number of the smallest currency unit:
total: 4999, not"49.99". Every place that formats or adds up a price must change. See “Moving to 2.0”, step 2
- Removed —
7.Links
- Every version heading links to its diff —
[2.7.0]: …/compare/v2.6.2...v2.7.0— and[Unreleased]compares the last tag withmain
- Every version heading links to its diff —
A git log is not a changelog — and neither are release notes
The word has two meanings, and that is half of the confusion. The GNU Coding Standards ask for a ChangeLog file that records every change to every source file, so that “people investigating bugs in the future” can trace them. Today they accept the version-control log in its place. The same standards ask for a second file, NEWS: “a list of user-visible changes worth mentioning”, newest first. What Keep a Changelog calls a changelog is the second file under the first file’s name. Olivier Lacan first published the convention in 2014, and it defines the document as “a curated, chronologically ordered list of notable changes for each version of a project”.
Changelog, version 2.7.0
- Added —
getCustomerOrders(customerId);getOrdersstopped at 100 orders - Deprecated —
address.zip; readaddress.postcode - Security — email addresses no longer reach the debug log
The commits behind it
- feat: paginate history
- feat(address): add postcode
- fix: redact pii in debug
- refactor: split http module
- Merge branch 'feat/postcode'
Read the right-hand column as one of the four consuming teams. Nothing in it says that zip will disappear, which is the one thing that creates work. The third commit is a security fix filed as an ordinary fix. And the last two say nothing at all to anyone outside the team.
Four documents, four readers
Most bad changelogs are one file trying to be several. The four documents below differ in who reads them, when that person opens them and what they need to find.
| Document | Reader | Opened when | Contains | Written |
|---|---|---|---|---|
| Commit log | The team, a year later | Tracing a bug to the change that caused it | Every step, in order, in the team’s words | With each change, by its author |
| Changelog | A developer who depends on you | Before an upgrade, or after one broke something | Every notable change, one line each, grouped by type | With each change, edited at release |
| Release notes | Users who did not follow the work | When the release is announced | The few changes worth announcing, explained | At release, by product or marketing |
| Migration guide | Someone in the middle of a major upgrade | When the build has just failed | For each breaking change: before, after, and how to check | With the breaking change, before the major ships |
The reorder release from the Release Planning page produces three of them. Line 2.1 above is the changelog line for the four consuming teams. Customers get a release note instead: “Repeat a past order from your order history in two taps. Anything out of stock is named, with a substitute.” That is line 1.4 of the release plan. And when 3.0.0 removes zip, the migration guide gets a step with the old read, the new read, and a search to find every remaining use.
The version number is a promise
Semantic Versioning turns three numbers into a compatibility contract. Tom Preston-Werner wrote it; version 1.0.0 of the specification was tagged in September 2011 and 2.0.0, still current, in June 2013. MAJOR goes up for incompatible changes to the public API, MINOR for backward-compatible functionality, PATCH for backward-compatible bug fixes. The point is not tidiness. A reader can decide whether an upgrade is safe without reading the diff, and a package manager can decide it on their behalf.
The rule that carries the weight: if existing users must change anything to keep working, that is a MAJOR bump, however small the change looks from the inside. Renaming a field, tightening a validation rule, making an optional parameter required and changing a default are all breaking changes. Shipping one of them as a patch is how a dependency earns its reputation.
| Change | Bump | Why |
|---|---|---|
| Add a function or an optional field (3.1, 3.2) | MINOR | Nothing that worked stops working |
| Mark something deprecated (3.3) | MINOR | The specification requires it, in item 7 |
| Make the code do what the docs say (4.1) | PATCH | Unless many users rely on the bug — then, in the FAQ’s words, “use your best judgment” |
| Remove or rename anything public (6.1) | MAJOR | Every use of it stops compiling or starts failing |
| Change a type or a format (6.2) | MAJOR | The code still runs and computes the wrong thing |
| Make a parameter required, or validate input more strictly | MAJOR | Calls that were accepted are now rejected |
| Change a default value | MAJOR | Code that never set it now behaves differently — and tools file it as a fix |
| Drop an old runtime, such as a Node.js version | MAJOR | By convention — the Conventional Commits spec uses exactly this as its example |
Change how the package is loaded — an ES module, an exports map | MAJOR | Imports that worked stop resolving; see is-promise below |
| Update a dependency without changing the public API | PATCH or MINOR | The FAQ: compatible, and the reason for the update decides which |
Zero, pre-release and build metadata
Anything starting with 0 is explicitly outside the promise: during 0.y.z, item 4 says, “Anything MAY change at any time.” That is honest while a design is still moving and dishonest once people depend on it. The specification’s own FAQ is blunt: “If your software is being used in production, it should probably already be 1.0.0.” A project that has sat on 0.x for four years with production consumers is using the zero to avoid the commitment.
A pre-release is marked with a hyphen — 3.0.0-rc.1 — and sorts before the release it precedes, so a package manager asking for 3.0.0 or higher will not pick it up by accident. Build metadata is marked with a plus — 3.0.0+build.482 — and is ignored entirely when versions are compared. That makes it the right place for a commit hash and the wrong place for anything a user needs to know. And a released version is never changed: if 2.6.1 turns out to be broken, the fix is 2.6.2, and 2.6.1 stays in the file, marked, as group 5 does.
The contract and everything else people depend on
SemVer can only promise what the public API says. Hyrum Wright, then at Google, put the problem in one sentence that Titus Winters later named Hyrum’s Law: “With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.” The order of results, the time a call takes, the exact wording of an error — somebody’s code reads it.
Package managers make that sharper. In npm, a dependency written as ^2.6.0 accepts any 2.x.y at or above 2.6.0, so a new minor reaches every consumer on its next fresh install, and nobody reads the changelog first. A lockfileexact versions of every dependencyA file the package manager writes next to the list of dependencies, recording the exact version of every package installed — including the ones your dependencies pulled in. Committed to the repository, it makes every install identical; without it, two builds of the same commit can run different code.All terms holds the old version until someone updates it, which delays the surprise but does not cancel it. That is why a MINOR that breaks something travels further and faster than a MAJOR ever could.
Other numbering schemes, and what each one promises
| Scheme | Example | The number tells you | Compatibility is promised by |
|---|---|---|---|
| Semantic Versioning | Libraries and packages: 2.7.0 | What kind of change it is | The number itself |
| Calendar versioning | Ubuntu 24.04 (year and month); pip 25.2 (year and release) | How old it is | A separate policy — pip deprecates for at least six months |
| Dated API versions | Stripe: 2026-09-30.endive | Which behaviour you asked for | Pinning: each account stays on its version until it upgrades |
| One rising number | Chrome and Firefox, every four weeks | Only the order of releases | Deprecation processes for web features, not the number |
| Major version in the import path | Go modules: example.com/lib/v2 | Which incompatible line this is | The path: “If an old package and a new package have the same import path, the new package must be backwards compatible” |
Stripe’s scheme is worth a second look, because it answers the question SemVer leaves to the consumer: when do I take the breaking change? Every account is pinned to the API version that was current when it started, and a request can ask for another with the Stripe-Version header. By 2017 Stripe had shipped “almost a hundred backwards-incompatible upgrades” that way without breaking anyone who did not opt in. Since 2024-09-30.acacia, monthly versions carry no breaking changes, and a major release with breaking changes comes twice a year.
Writing an entry someone can act on
Keep a Changelog groups every release under six headings. Using the same six everywhere means a reader can scan for the one they care about — usually Removed and Changed, because those create work.
| Heading | For | The reader asks | On this page |
|---|---|---|---|
| Added | New features | Is there something I can now do? | 2.1, 3.1, 3.2 |
| Changed | Existing behaviour that is now different | Will my current code behave differently? | 6.2 |
| Deprecated | Still works, will be removed | What must I move off, and by when? | 2.2, 3.3 |
| Removed | Gone in this version | What breaks the moment I upgrade? | 6.1 |
| Fixed | Bugs resolved | Can I drop my workaround? | 4.1, 5.1 |
| Security | Vulnerabilities closed | Am I affected, and how urgently must I upgrade? | 3.4 |
The rest of the convention is mechanical and worth following exactly, because it is what lets a reader find things. Its guiding principles: changelogs are for humans, not machines; an entry for every single version; the same types of change grouped; versions and sections linkable; the latest version first; the release date of each version shown; and a statement of whether the project follows Semantic Versioning. Dates are ISO 8601 — 2026-09-15, not 15/09/26, which reads as two different days depending on the reader’s country. An Unreleased section sits on top, and a pulled version stays in the file with a loud [YANKED] tag.
Eight entries as teams write them, and the rewrite
| As written | What the reader cannot tell | Rewritten |
|---|---|---|
| “Bug fixes and improvements” | Anything at all | One line per change, as in 4.1 |
| “Updated the token checks” | Whether it affects them | The effect and the condition — see the example below |
| “Refactored the HTTP layer” | There is nothing to observe | Delete the line |
| “Fixed #4812” | What was broken | Name the symptom: “getOrders stopped at 100 orders without saying so”, as in 3.1 |
| “Removed legacy endpoint” | Which one, and what instead | Name it, its replacement and the guide step, as in 6.1 |
| “Performance improvements” | Where, and by how much | “getCustomerOrders for 500 orders: 280 ms, was 1.9 s” |
“Deprecated zip” | What to use instead, and until when | Replacement, removal version, earliest date, as in 3.3 |
| “Security fixes” | Whether they are affected, and how urgent it is | What leaked or was possible, under which setting, and who must upgrade, as in 3.4 |
Two habits do most of the remaining work. Keep the Unreleased section filled as changes land, so each entry is written while the change is fresh, by the person who made it. And write each line as the effect on the reader, not as the edit in the code. A changelog assembled the day before release is always the thin one, because whoever assembles it is reconstructing a month from commit subjects.
Deprecation — the part that buys goodwill
Removed is the heading that creates work; Deprecated is the heading that gives people time to do it. A deprecation is a promise in three parts, and a notice missing any of them is a complaint about your own code.
- What is going away, named precisely enough that a reader can search their own code for it —
address.zip, not “the old address fields”. - What to use instead, available now rather than promised for later — line 3.2 shipped
postcodein the same release that deprecatedzip. - When it will be removed, as a version and an earliest date rather than a season — “3.0.0, and not before 15 March 2027”.
SemVer builds the sequence in. Item 7 says a minor release MUST be made when anything public is marked deprecated, and the FAQ asks for “at least one minor release that contains the deprecation” before the major that removes it. Keep a Changelog states the goal from the reader’s side: “It should be possible to upgrade to a version that lists deprecations, remove what’s deprecated, then upgrade to the version where the deprecations become removals.” Line 1.3 adds a floor in time, because a consumer that upgrades twice a year might otherwise meet the deprecation and the removal in one step.
| Project | Signal where the code runs | Minimum before removal |
|---|---|---|
| Python (PEP 387) | A DeprecationWarning at runtime | Two minor versions; five years preferred. distutils: deprecated in 3.10, removed in 3.12 |
| Node.js | Four kinds, numbered from DEP0001: documentation-only, application, runtime, end-of-life | No fixed window; a deprecation moves up the kinds before removal |
| Kubernetes | Since v1.19, a warning on every request to a deprecated API, and a metric of who still calls it | Beta: 9 months or 3 releases, whichever is longer. GA: never within a major version |
| pip | A warning when the behaviour is used | Six months; three when two maintainers agree to fast-track it |
| Stripe | None needed: the account stays on its pinned version | Until the customer upgrades |
| Any HTTP API | Deprecation (RFC 9745, 2025) and Sunset (RFC 8594, 2019) response headers | Whatever the provider publishes — the headers carry the dates |
Deprecating an HTTP endpoint
The endpoint behind line 2.2, GET /orders?customer=, can say so in every response. Deprecation: @1789430400 gives the moment it was deprecated, 15 September 2026, as Unix seconds after an @. Sunset: Mon, 15 Mar 2027 00:00:00 GMT gives the earliest moment it may stop answering, in the older HTTP-date format. A Link header with rel="deprecation" points to the page that explains the move. RFCRequest for Comments — a numbered internet standardA numbered document published by the IETF, the body that standardises internet protocols; HTTP, TLS and JSON are all defined in RFCs. A published RFC never changes — a correction is a new RFC with a new number, which is why “RFC 9110” names one exact text.All terms 9745 adds two rules worth knowing. Deprecation “does not change any behavior of the resource”, and the Sunset date must not be earlier than the Deprecation date.
A header marks a resource, not a field, so it cannot announce line 3.3. address.zip is announced in the changelog and the docs, and the library logs a warning the first time a process reads it. That warning is also how the team learns who still depends on it.
How far automation honestly goes
Conventional Commits 1.0.0, published in 2019 and grown out of the Angular project’s commit convention, standardises the first line of a commit message so a tool can read it: a type, an optional scope, an optional ! for a breaking change, then the description — “feat(address): add postcode”, “fix!: require a currency on every price”. A breaking change can also be stated in a footer beginning BREAKING CHANGE:, which is the better place when it needs a sentence of explanation. Types other than feat and fix are allowed and, the spec says, “have no implicit effect in Semantic Versioning”.
| Commit | Implies | Lands under |
|---|---|---|
| fix: | PATCH | Fixed |
| feat: | MINOR | Added |
| ! or BREAKING CHANGE: | MAJOR | Changed or Removed |
| chore:, docs:, refactor:, test: | Nothing | Usually nowhere — no effect a user can see |
| Deprecated, Security | No type of their own | A person has to put them there — the commit in the comparison above was a plain fix |
This genuinely helps. A tool can derive the next version number and assemble a draft under the right headings, which removes the two most tedious steps. What it cannot do is decide whether a change is breaking, because that depends on how people use the thing, and that knowledge lives outside the repository. A commit marked fix: that changes a default value is breaking, and the tool will call it a patch with full confidence.
| Tool | Reads | Produces | Where a person decides |
|---|---|---|---|
| semantic-release | Commit messages | The version, the notes and the published package, with no human step | Only in each commit message; its README says it “removes the immediate connection between human emotions and version numbers” |
| release-please | Conventional Commits | A release pull request that keeps the changelog and the bump up to date | Editing and merging that pull request |
| Changesets | A small Markdown file per change: the bump and the summary | The changelog and the new versions | In the pull request, by its author, reviewed with the code |
| towncrier | “News fragments” — one file per change, written for end users | The news file at release; used by pip, pytest and Twisted | In the pull request, like Changesets |
The last two answer the problems the earlier sections raised. The entry is written when the change is fresh, by the person who understands it, and reviewed in the same pull requesta change proposed for reviewA request to merge one branch into another, where the change is reviewed, discussed and checked by the pipeline before it reaches the main branch; GitLab calls the same thing a merge request. Its cost is waiting: a pull request that sits unreviewed for days is where long-lived branches and painful merges come from.Full article →All terms as the code — so the reviewer who sees a default change also sees the line that calls it a patch. And because each change brings its own file, there is no single CHANGELOG.md that every branch edits and every merge conflicts on. The towncrier README puts the reason plainly: it delivers the news “convenient to those that hear it, not those that write it.”
Four releases where the changelog line was the problem
Each case below is documented by the project itself. In none of them was the code the whole story. Each time, the number, the line or the signal that should have reached the reader did not.
| Case | What happened | What the reader needed | Lines |
|---|---|---|---|
| is-promise, 25 April 2020 | A minor release changed how the package loads; popular tools broke on fresh installs for three hours | A MAJOR number | 1.2, 6.2 |
| Kubernetes 1.16, 18 September 2019 | APIs deprecated for almost two years stopped answering; many users first heard of it then | A signal where the code runs, not only a line in release notes | 2.2, 3.3 |
| Kubernetes and dockershim, December 2020 | A correct deprecation line was read as “Kubernetes drops Docker” | Who is affected — and a separate document for everyone who is not | 3.4 |
| Log4j, December 2021 | Four releases in eighteen days, each the safe one until the next | Affected and fixed ranges for every supported line | 3.4 |
is-promise, 2020 — a minor that was a major
is-promise is a one-line library: a function that says whether a value is a promise. In the week before 25 April 2020 it was downloaded about 11.7 million times, almost all of it as a dependency of something else. That day at 15:03 UTC, its maintainer published 2.2.0, the first release since September 2015. It added an ES module entry point, with "type": "module" and an exports map in package.json, and the map was malformed. Every project depending on ^2.1.0 took the new minor on its next fresh install. Freshly installed copies of Create React App, Angular’s command-line tool and Firebase’s tools stopped working.
2.2.1 followed at 17:55 and 2.2.2, without exports, at 18:08 — about three hours in all. The maintainer’s own comment on the issue is the useful part: “I read the documentation, and then made this change thinking it was pretty safe.” The next day the same exports change was published again, correctly formed, as 3.0.0. A change to how a package is loaded is part of its public API, and the number has to say so.
Kubernetes 1.16, 2019 — two years of notice nobody saw
Kubernetes 1.16, released on 18 September 2019, stopped serving Deployments, DaemonSets, StatefulSets and ReplicaSets from the old extensions/v1beta1 and apps/v1beta1/v1beta2 API versions. The replacement, apps/v1, had been available since v1.9 in December 2017. The deprecation was in the release notes, and the project published a blog post in July warning about the removal. Manifests and Helm charts still written against the old versions failed after the upgrade all the same.
The project’s answer, in v1.19 (2020), moved the notice to where the code runs. Every request to a deprecated API now returns a warning header, which kubectl prints, naming the release that will stop serving it and the replacement. A metric, apiserver_requested_deprecated_apis, tells administrators which deprecated APIs are still being called. That is line 2.2’s warning per process and the Sunset header, at the scale of a whole ecosystem.
dockershim, 2020 — a correct line, read wrongly
The v1.20 release notes, in December 2020, deprecated dockershim: the adapter that let the kubelet use Docker Engine as its container runtime. The line was accurate. It was read as “Kubernetes is dropping Docker”, and what the project later called panic turned into “a conflagration on social media”. Most readers were not affected at all, because images built with Docker run on every runtime. The project wrote “Don’t Panic: Kubernetes and Docker” and an FAQ. In 2022, when v1.24 removed the shim, a project post said it plainly: “We didn’t do a great job communicating this … This was our fault.”
The changelog line was the right document for cluster operators. Everyone else needed a different one — release notes saying who is affected and who is not — and it arrived after the line had already been misread. When a deprecation touches a name people recognise, write that second document first.
Log4j, 2021 — the Security heading under pressure
CVECommon Vulnerabilities and Exposures — a public ID for a known vulnerabilityA public identifier for a known security flaw, such as CVE-2021-44228 for Log4Shell, so that vendors, scanners and advisories all mean the same bug. The number says a flaw exists; how dangerous it is for you depends on whether you run the affected version and expose the affected part.All terms-2021-44228 in Log4j 2, scored 10.0, allowed remote code execution through a logged string. Version 2.15.0 reached Maven Central on 9 December 2021. Further problems followed, and so did 2.16.0 on 13 December, 2.17.0 on 18 December and 2.17.1 on 27 December — each of them “the safe version” until the next. Older Java lines got their own fixes: 2.12.x for Java 7 and 2.3.x for Java 6. For three weeks, teams around the world opened one page again and again to answer two questions: am I affected, and what do I move to?
The Apache Logging security page answers them in a fixed shape for every vulnerability: the affected range, the fixed version for each Java line, and a mitigation for those who cannot upgrade. That is the shape a Security entry needs, scaled down. Line 3.4 says what leaked, under which setting, and who must upgrade. “Security fixes” would answer neither question.
Where it degrades — and what a changelog cannot do
The test of a changelog is whether anyone opens it under pressure. Something broke after an upgrade at 16:40, and a person who has never read your code opens the file. In under a minute they need to find whether the version they moved to changed the thing in front of them. Everything in the convention — newest first, six fixed headings, one line per change, effects rather than edits — exists to make that minute possible. These are the ways it stops being possible.
- A generated commit dump published unedited, which nobody reads twice.
- Breaking changes shipped as a minor or a patch, which spend the trust the number was supposed to carry.
- No declared public API, so every refactoring is arguably breaking and nobody can tell.
- Years on 0.x with production consumers, which uses the zero to dodge the commitment.
- Deprecations with no replacement or no removal date, which nobody acts on until the removal lands.
- A removal timed by the calendar alone, while someone is still calling the thing.
- Security entries that do not say who is affected, which turn every reader’s afternoon into an investigation.
- A broken version deleted instead of yanked, so the people who pinned it cannot learn why it vanished.
When to use it
Use it when
- Other people or teams depend on your software and decide for themselves when to upgrade.
- You publish a library, an API or anything else with versions.
- Support needs to know which version a reported problem belongs to.
- You deprecate things, and need one place where the dates live.
Avoid it when
- An internal module with one consumer sitting next to you — a message is cheaper.
- A continuously deployed web app whose users never choose a version — write release notes instead.
- It would be generated and never edited — that is maintenance without a reader.
- Release notes for the same readers already cover every change — do not keep two overlapping documents.
Sources and method
What this page stands on
- 28 sources
- 4 documented failures
- Checked October 13, 2026
The failures this page learns from
the section “Four releases where the changelog line was the problem”- Postmortemthen/is-promise — Issue #13: ERR_INVALID_PACKAGE_TARGET (opens in a new tab)GitHub · Apr 25, 2020
- Postmortemthen/is-promise — Issue #20: "exports" config (opens in a new tab)GitHub · Apr 2020
- ArticleVallery Lancey — Deprecated APIs Removed In 1.16: Here’s What You Need To Know (opens in a new tab)Kubernetes Blog · Jul 18, 2019
- ArticleJordan Liggitt — Warning: Helpful Warnings Ahead (opens in a new tab)Kubernetes Blog · Sep 3, 2020
- ArticleDon’t Panic: Kubernetes and Docker (opens in a new tab)Kubernetes Blog · Dec 2, 2020
- PostmortemKat Cosgrove — Dockershim: The Historical Context (opens in a new tab)Kubernetes Blog · May 3, 2022
- DocumentationApache Log4j — Security (opens in a new tab)Apache Logging Services
- DocumentationMaven Central — org/apache/logging/log4j/log4j-core (opens in a new tab)Maven Central
Practices and where the terms come from
the format, the version number and what an entry is- StandardOlivier Lacan — Keep a Changelog 1.1.0 (opens in a new tab)keepachangelog.com · Feb 15, 2019
- StandardTom Preston-Werner — Semantic Versioning 2.0.0 (opens in a new tab)semver.org · Jun 18, 2013
- StandardConventional Commits 1.0.0 (opens in a new tab)conventionalcommits.org · 2019
- GuideGNU Coding Standards — 6.7 NEWS File (opens in a new tab)Free Software Foundation
- GuideGNU Coding Standards — 6.8 Change Logs (opens in a new tab)Free Software Foundation
- ArticleHyrum Wright — Hyrum’s Law (opens in a new tab)hyrumslaw.com
Deprecation and versioning policies
the section “Deprecation — the part that buys goodwill”- StandardRFC 9745 — The Deprecation HTTP Response Header Field (opens in a new tab)IETF · Mar 2025
- StandardRFC 8594 — The Sunset HTTP Header Field (opens in a new tab)IETF · May 2019
- StandardPEP 387 — Backwards Compatibility Policy (opens in a new tab)Python Software Foundation
- StandardPEP 632 — Deprecate distutils module (opens in a new tab)Python Software Foundation
- DocumentationNode.js — Deprecated APIs (opens in a new tab)Node.js
- Documentationpip — Release process (opens in a new tab)Python Packaging Authority
- DocumentationKubernetes Deprecation Policy (opens in a new tab)Kubernetes
- ArticleBrandur Leach — APIs as infrastructure: future-proofing Stripe with versioning (opens in a new tab)Stripe · Aug 2017
- DocumentationStripe API Reference — Versioning (opens in a new tab)Stripe
- DocumentationGo Modules Reference (opens in a new tab)The Go Programming Language
The tools that draft a changelog
the section “How far automation honestly goes”Researched with Claude. Finding and cross-checking the primary sources was done with Claude (Anthropic). Selection, writing and fact-checking: the Anatomy of IT Product editors.
Found this useful?
Share it with someone who is working on the same problem.