IAnswerable

Semantic Versioning in Practice

28 Aug 2026

Semver looks simple until you’re staring at a diff deciding if it’s a minor or a patch. The spec is short, but the judgment calls it hands you are not. Here’s how to make them consistently.

The three numbers, briefly

MAJOR.MINOR.PATCH. Increment MAJOR when you break compatibility, MINOR when you add functionality that’s backward-compatible, PATCH when you fix a bug without changing the API. That’s the whole spec. The hard part is deciding what counts as “breaking.”

What actually counts as breaking

The rule that resolves most of the grey area: if a caller who does nothing sees different behavior, it’s breaking. If they only see new behavior by opting into it, it’s not.

Pre-1.0 is not a free pass

0.x.y technically means “anything can break at any time” under the spec, and teams use that as license to skip discipline entirely. Don’t. If you have real users on 0.4.0, breaking them without a changelog entry costs you the same trust it would at 1.4.0. Treat 0.x minor bumps like 1.x major bumps in practice — bump MINOR for anything breaking, and write the migration note. Save the “anything goes” version of pre-1.0 for the period before anyone outside your team is depending on it.

Version the thing your users see, not your internals

If you ship a CLI, a library, and a hosted API from one repo, don’t force them onto one shared version number just because it’s easier to manage. A patch to the CLI’s help text shouldn’t bump your API’s major version, and a breaking API change shouldn’t silently ship inside a CLI patch release. Version each surface independently, or you’ll train users to ignore version bumps because half of them don’t apply.

Automate the bump, don’t negotiate it

Use conventional commits (fix:, feat:, feat!: or BREAKING CHANGE: in the footer) and a release tool that reads them — semantic-release or release-please both work — so the version number is a mechanical output of your commit messages, not a debate in the release PR. This also forces the judgment call earlier: you decide “is this breaking” at commit time, when you have full context, instead of at release time, when you’re skimming a diff.

git commit -m "fix: webhook retry no longer duplicates events"
git commit -m "feat: add CSV export to usage report"
git commit -m "feat!: remove legacy /v1/users endpoint

BREAKING CHANGE: use /v2/users instead."

The gotcha

The most common semver mistake isn’t picking the wrong number — it’s forgetting that a security fix which also changes behavior is still breaking. “It’s a security patch” doesn’t exempt you from the major bump; it just means you should also expedite the migration guide.