Skip to main content

Semantic Versioning, Changelogs, Release Notes

What This Concept Is

Releases are not just "the latest build." They are a named, versioned, documented event that downstream users can reason about.

  • Semantic Versioning (SemVer 2.0.0) -- version numbers of the form MAJOR.MINOR.PATCH with precise rules:
    • MAJOR bump when you make an incompatible API change
    • MINOR bump when you add backward-compatible functionality
    • PATCH bump when you make backward-compatible bug fixes
    • optional -pre.release (1.2.0-rc.1) and +build.metadata (1.2.0+sha.abcd)
  • Changelog -- a human-readable, chronologically ordered file (CHANGELOG.md) describing what changed per version. Not a dump of git log.
  • Release notes -- user-facing communication of a release: highlights, breaking changes, migration guidance. Usually drawn from the changelog plus prose.
  • Git tags -- the git primitive that names a release in the repo. Pro Git's tagging chapter distinguishes lightweight (just a pointer) from annotated (an object in the repo, with message, author, date, and optional GPG signature). Releases should use annotated, signed tags.

Together they form the release contract: a promise to downstream users about what the version number means.

Why It Matters Here

SemVer is how compatibility is communicated across organizational boundaries. A library that breaks its public API without a major bump breaks its users' builds. A service that silently breaks its HTTP contract without a major bump breaks its clients' production. In both cases, the damage is not technical, it is a broken promise.

Release notes and changelogs serve a different audience -- humans making upgrade decisions:

  • "Is this safe to take? What breaks?"
  • "What's the migration effort?"
  • "What regression risk am I accepting?"

Without a changelog, every consumer has to re-discover the release contents by reading commits. Multiply across consumers -- that is real engineer-years of waste. Pro Git's shortlog section gives the git primitive for summarizing "what's new since the last tag" -- but git shortlog is raw material for a changelog, not the changelog itself.

Concrete Example: SemVer Decisions

ChangeBump
Fix a bug where /orders returned 500 on empty cartPATCH
Add a new optional query param ?include=items to /ordersMINOR
Remove the legacy_token field from responsesMAJOR (breaking)
Rename a field from orderId to order_idMAJOR
Add a new required field to a request bodyMAJOR
Make an HTTP endpoint stricter about input validationUsually MAJOR (accepted inputs that now fail)
Internal refactor, same public APIPATCH or MINOR depending on convention

Note that additive changes to requests (new required field) are breaking, while additive changes to responses (new field) are usually not -- consumers ignore fields they don't know.

Concrete Example: Changelog (Keep a Changelog format)

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [2.3.0] - 2026-04-15
### Added
- `?include=items` query parameter on `/orders` to fetch line items in one call.
- OpenAPI schema published at `/openapi.json`.

### Fixed
- `/orders` no longer returns 500 on carts with zero items.
- Timezone handling for `created_at` now respects the `X-Timezone` header.

### Deprecated
- `legacy_token` response field will be removed in 3.0.0. Use `access_token`.

## [2.2.1] - 2026-03-30
### Fixed
- Race condition in retry logic that could double-charge under specific
concurrent conditions.

### Security
- Upgraded `openssl` to address CVE-2026-XXXXX.

Good changelogs are grouped by type (Added / Changed / Deprecated / Removed / Fixed / Security), dated, and linked.

Automating the Release Cut

Common pattern in modern pipelines:

  • developers write Conventional Commits (feat:, fix:, feat!: with breaking-change footer)
  • a tool (release-please, semantic-release, changesets) parses commits, computes the next version, updates CHANGELOG.md, opens a release PR
  • merging the release PR tags the repo (annotated + signed) and publishes the artifact

The shell glue for cutting a release is straightforward -- essentially, git tag -s, git push --tags, then a build job triggered by the tag -- but the value is the policy that every commit since the last tag is surfaced and classified, not silently bundled.

# Annotated, signed tag for a release
git tag -s v2.3.0 -m "Release 2.3.0"
git push origin v2.3.0

# Or, summarize commits since last tag for the changelog
git shortlog --no-merges v2.2.1..HEAD

Common Confusion / Misconception

"SemVer only applies to libraries." It applies to any artifact with external consumers -- APIs, CLIs, wire protocols, Kubernetes CRDs, database schemas. If something outside you depends on it, SemVer applies.

"We'll bump the version when the release feels significant." That is not SemVer. Under SemVer, any incompatible change forces a major bump, even a one-character rename of a public field. "Feels" has nothing to do with it.

"Internal services don't need SemVer." They do if they are consumed by anyone else, even another team in the same company. The boundary of "internal" is smaller than people think.

"The changelog is git log." No. Git log is developer-facing noise. Consumers need grouped, contextual summaries at release granularity. Keep a Changelog says this bluntly: "Don't let your friends dump git logs into changelogs."

"Pre-1.0 anything goes." Common but dangerous practice. Pre-1.0 is explicitly "unstable," but still publish a changelog. The moment you have real users, you have a compatibility contract even if you have not declared one.

"Lightweight tags are fine for releases." They will work, but they carry no message, no author, no date, and cannot be GPG-signed. For any release you or a consumer might need to verify or audit later, use git tag -a (annotated) or git tag -s (signed). Pro Git's signing chapter walks through the GPG setup.

"CalVer makes SemVer obsolete." Different product shapes. Browsers and OS distributions use calendar versions (2026.04.01) because consumers don't make SemVer-style upgrade decisions -- they upgrade on cadence. Libraries and APIs almost always still want SemVer.

How To Use It

In practice:

  1. Decide on the public surface area that SemVer applies to. Write it down.
  2. Maintain CHANGELOG.md with an [Unreleased] section. Update it in the same PR as the change.
  3. On release: move [Unreleased] entries under the new version, commit, tag (git tag -s v2.3.0), and cut release notes from the changelog.
  4. Use Conventional Commits if you want to automate the version bump: feat: -> MINOR, fix: -> PATCH, feat!: or BREAKING CHANGE: -> MAJOR.
  5. Automate generation where possible -- release-please, semantic-release, changesets -- but do not skip the human review of the release notes.

Check Yourself

  1. State the three rules of SemVer in one sentence each.
  2. Does adding a new optional query parameter require a MAJOR bump? What about a new required one?
  3. What is wrong with generating the changelog from git log without curation?
  4. Give one case where Conventional Commits automates the correct version bump.
  5. Why should release tags be annotated (and signed) rather than lightweight?

Mini Drill or Application

Take three recent PRs from a project you work on. For each, decide:

  • what version bump it would cause under SemVer
  • what changelog section it belongs to (Added, Changed, Fixed, etc.)
  • what release-note sentence you would write for a user reading this later

Compare your answers with a teammate. The disagreement is where the project's release contract is implicit.

Read This Only If Stuck

See also (external)