SG · 01° 17′ N
§ F05 · Field Note PUBLISHED
← Index

Release Notes as Code — Turn Changelogs Into a Reliable Product Pipeline

Every team writes release notes. Almost every team writes them poorly. The reason is structural: release notes are usually written after the fact, by the wrong people, from incomplete memory, hours before a deadline. The output is what you’d expect.

The fix is treating release notes the same way you treat any other piece of versioned content: they belong in the repo, they belong to the PR that introduces them, and they should be assembled by a machine.

What we do

Every PR that ships user-visible behavior includes a release note fragment as part of the change. The fragment is a small, structured file checked in alongside the code:

---
type: feature       # feature, fix, change, deprecation, security
audience: customer  # customer, internal, developer
summary: Reports now export as Parquet in addition to CSV.
ticket: PROD-1487
breaking: false
---

We’ve added Parquet export to the reports API. Existing CSV
exports continue to work unchanged. To switch, set
format=parquet on the export call.

CI rejects PRs that touch user-facing code without a fragment, or labels them internal-only if the author asserts that. At release time, a build step concatenates fragments by version, groups them by type, and produces:

  • The public CHANGELOG.md.
  • The customer email release notes (audience=customer).
  • The internal release tracker entry (everything).
  • The in-app changelog modal content.

One source, four outputs. All deterministic. All reproducible from git.

Why this works better than a Friday writeup

The information is fresh. The author writes the note while they remember why they wrote the code. Not a week later, not from someone else’s commit messages.

Categorization is correct. Authors mark their own type and audience. They know if a thing is a fix or a feature; the release manager doesn’t.

It’s reviewable. The release note fragment goes through code review with the code. Reviewers catch marketing-speak, ambiguity, and missed breaking-change flags before merge, not after.

It’s reproducible. Need to rebuild the changelog for v1.4.2 because someone deleted the public page? Check out the v1.4.2 tag and re-run the build. Done.

The snags we hit

Internal-only PRs. A lot of PRs aren’t user-visible. Don’t force fragments on those — let authors mark them as internal in the PR description and skip the requirement. A bot-enforced “fragment OR internal label” rule is enough.

Multi-audience entries. Some changes matter to both customers and internal teams. Allow multiple audiences per fragment; the build step deduplicates at output time.

Breaking changes. The breaking: true flag should fail CI unless a separate migration doc is also present. Breaking changes that ship without migration notes are how customer trust gets eroded.

Empty release windows. If a release has no user-visible fragments, don’t ship a vague “various improvements and bug fixes” — ship nothing. Silence is more credible.

Tooling reality

Several open-source tools do this well already (Towncrier, Reno, Changie). You don’t need to build the assembler yourself. You do need to build the discipline: enforce the fragment in CI, review fragments like code, run the build step on every release.

The deeper point

Changelogs are a customer-facing surface. They’re how users learn what changed and decide whether to upgrade. Treating them as a last-minute writeup signals that you don’t take that surface seriously. Treating them as a build artifact signals that you do.