Team Workflows
Topic 44

Conventional Commits and SemVer

Workflow

Conventional Commits is a commit-message format that machines can parse: a type(scope): description first line plus optional footers. Semantic Versioning is a numbering contract, MAJOR.MINOR.PATCH, that tells consumers what a version bump means. Together they let tooling derive the next version and the changelog directly from your commit history.

The payoff is that releases stop being a manual judgment call. Once every commit declares its kind, a tool can read the log, decide whether the change is a patch, a feature, or a break, tag the version, and write the notes — no human deciding "is this a minor or a major?"

The Conventional Commits Format

The first line is a type, an optional scope in parentheses, and a short description. Common types are feat, fix, chore, and docs. A breaking change is flagged with a ! after the type or a BREAKING CHANGE: footer in the body.

feat(auth): add OAuth login

BREAKING CHANGE: removes the legacy token endpoint

The structure is the whole point — a parser can read the type and the breaking-change marker without understanding the prose, which is what makes the downstream automation possible.

Semantic Versioning Rules

SemVer maps change kinds to version components: a fix bumps the patch number, a feat bumps the minor, and a breaking change bumps the major. A major bump is a promise to consumers that something they depend on has changed in an incompatible way.

That promise runs both directions. Bumping major when nothing broke tells everyone to brace for a migration that does not exist; leaving it at minor when something did break sends the break out silently.

Deriving Versions Automatically

Because the mapping is mechanical, tooling can compute the next version from the commits since the last release: any feat means at least a minor, any breaking change means a major, otherwise a patch. The one caveat is 0.x — under SemVer, anything below 1.0.0 is allowed to break in a minor bump, so the usual guarantees do not yet apply.

Automated Changelogs and Releases

Tools like semantic-release and release-please read the commit history, decide the version, tag it, and generate release notes grouped by type. The changelog becomes a byproduct of how you already commit, not a document someone maintains by hand and forgets to update.

Enforcement

None of this works if half the team forgets the format. A commitlint check in a commit-msg hook, or a required PR-title check, makes the convention non-optional — malformed messages are rejected before they reach main, so the automation never silently skips a change.

Common Mistakes
  • Marking a breaking change as feat without ! or a BREAKING CHANGE: footer, so automation cuts a minor release and consumers' caret ranges silently pull the break.
  • Bumping the major version for a bugfix "to feel safe", which tells every consumer to expect breaking changes that do not exist.
  • Treating 0.x as stable, when under SemVer anything below 1.0.0 can break in a minor bump.
  • Squash-merging without curating the squash message, so the auto-generated changelog inherits "fix stuff" from the WIP commits.
  • Adopting Conventional Commits with no lint check, so half the team forgets the format and automated versioning skips their changes.
Best Practices
  • Enforce Conventional Commits with commitlint in a commit-msg hook or a required PR-title check.
  • Automate versioning and changelogs with semantic-release or release-please driven off commit types.
  • Mark breaking changes explicitly with ! and a BREAKING CHANGE: footer so automation cuts a major.
  • Curate the squash-merge commit message into a valid Conventional Commit so the changelog stays accurate.
  • Reach 1.0.0 before promising stability, and treat every 0.x minor as potentially breaking.
Comparable toolssemantic-release commit-driven versioning and notesrelease-please release PRs from commit historyChangesets popular for JS monoreposcommitlint enforces the message format

Knowledge Check

Under SemVer, which component does a feat commit bump?

  • The minor version — a new backward-compatible feature
  • The patch version, treated the same as a fix since both ship small incremental changes
  • The major version, because any new code added to the public surface counts as a breaking change
  • None — features never change the version number

You ship a breaking change labeled feat with no ! or footer. What happens to consumers on caret ranges?

  • Automation cuts a minor release, and their ^ ranges silently pull in the breaking change
  • Their build refuses to install the new version and pins them to the last release before the break
  • A major version is cut automatically regardless of the label, because the tool detects the break itself
  • Nothing — caret ranges never upgrade across any change, so consumers stay on the version they first locked

Why is a 0.x release not considered stable under SemVer?

  • Below 1.0.0, even a minor bump is allowed to break, so the normal compatibility guarantees do not apply
  • Because 0.x versions cannot be published to a public registry until they first reach the 1.0.0 milestone
  • Because 0.x only allows patch releases, never minor ones, until the major is manually promoted to 1.0.0
  • It is stable — the leading 0 just signals the package is free to use rather than commercial or paid

How does an uncurated squash-merge message hurt an automated changelog?

  • The changelog inherits the squash commit's message, so noise like "fix stuff" from WIP commits ends up in the release notes
  • It causes the released version to skip a number, leaving a visible gap in the published version sequence
  • It deletes the previous changelog entirely and regenerates it from scratch using only the latest squash subject line as input
  • It has no effect, since changelog tools read the diff and the changed file names rather than the commit message subject text

You got correct