Ctrl K

Semantic Versioning for Releases

Decide which part of a MAJOR.MINOR.PATCH version to bump on each release based on the kind of change, including the pre-1.0 rules and the one hard rule for publishing.

Semantic versioning (semver) is the near-universal convention for naming releases. A version is MAJOR.MINOR.PATCH, and each release bumps one of the three parts based on what kind of change it is: a bug fix, a new feature, or a break in existing behavior. The point is to communicate intent to users from the version number alone, before they read a changelog. This document covers how to pick the part to bump, the reset rule, how the pre-1.0 phase differs, and the one rule that matters operationally when publishing to a package registry.

The three parts

The leftmost part that changes reflects the biggest kind of change in the release. Pick the leftmost part that applies.

PartBump whenExample
PATCH (0.3.1 -> 0.3.2)A bug fix or internal change, no new features, nothing breaksA platform-specific install fix; pruning dead code
MINOR (0.2.x -> 0.3.0)A feature is added and old usage still works (backward-compatible)An auto-detect step; a "show in folder" action
MAJOR (0.x -> 1.0, 1.x -> 2.0)A breaking change: an existing setup, API, or behavior stops working the way it didRenaming a command; changing the config format

The reset rule

When a part is bumped, every part to its right resets to 0.

  • 0.3.5 -> 0.4.0, not 0.4.5, when the middle part goes up.
  • 1.4.2 -> 2.0.0, not 2.4.2, when the major part goes up.

So the version is not three counters that each climb on their own. It is a single ranked statement about the release, read left to right.

Deciding each release

The whole decision each release comes down to one question about the change:

  • Added a feature that keeps old usage working? Bump MINOR.
  • Fixed or polished something without adding a feature? Bump PATCH.
  • Broke how people already use it? Bump MAJOR.

Pick the leftmost that applies, then reset everything to its right. Worked examples:

  • 0.2.2 -> 0.3.0 (MINOR): real new capabilities were added (auto-detect, reveal in folder, shareable filenames), and old usage still works. New capability -> bump the middle part.
  • 0.3.0 -> 0.3.1 (PATCH): a behavior refinement, such as changing when a fetch happens to respect a rate limit. It fixes and polishes rather than adding a headline feature, so a patch bump reads right.

The 0.x asterisk

While a project is pre-1.0 (0.y.z), the contract is looser: 0.x signals "still evolving, things may change." There is no MAJOR to lean on yet, so in practice:

  • The MINOR slot (0.3 -> 0.4) is used as the "breaking-ish or notable" bump.
  • The PATCH slot (0.3.1 -> 0.3.2) is used for small fixes.

Cut 1.0.0 when the project feels stable and you are committing to not breaking users. After 1.0.0, MAJOR strictly means "breaking change," and the full contract above applies.

The one hard rule for publishing

Semver is about communicating intent, so most of it is convention, not enforcement. But when a project publishes to a package registry (PyPI, npm, and similar), one rule is operationally hard: a registry will not let you republish a version number that already exists.

  • Every publish needs a new, higher version. This is the rule that keeps publishing working at all.
  • A release pipeline usually enforces it by requiring the git tag to equal the version declared in the project manifest (for example, the tag must match pyproject.toml before a publish job runs).

So even if the exact MAJOR/MINOR/PATCH choice is a judgment call about intent, the "must increment" part is not optional once a package is published.