Headroom Versioning Strategy: How Semantic Versioning is Automated with Release-Please

Headroom follows a fully automated semantic versioning (SemVer) strategy powered by Release-Please, deriving version numbers from three sources: the canonical definition in pyproject.toml, Git release tags, and Conventional Commit analysis.

The chopratejas/headroom repository implements a robust versioning strategy that eliminates manual version bumping while strictly adhering to Semantic Versioning standards. This approach ensures that every merge into the main branch results in a correctly incremented version based on the nature of changes committed. The entire workflow is orchestrated through the release automation module and integrated with GitHub Actions for continuous delivery.

Understanding Headroom's Semantic Versioning Approach

Headroom's versioning strategy combines manual canonical definitions with automated commit analysis. The system reconciles three distinct sources to determine the authoritative version string at runtime.

The Three Sources of Version Truth

Source Contribution Code Location
Canonical project version Base version defined in pyproject.toml [pyproject.toml](https://github.com/chopratejas/headroom/blob/main/pyproject.toml)
Git tags Historical tags (e.g., v0.26.0, v0.26.0.1) prevent backward steps [headroom/release_version.pyfind_latest_release_tag](https://github.com/chopratejas/headroom/blob/main/headroom/release_version.py#L11-L22)
Conventional Commits Commit messages analyzed for feat, fix, or BREAKING CHANGE markers [headroom/release_version.pyclassify_commit_bump](https://github.com/chopratejas/headroom/blob/main/headroom/release_version.py#L36-L55)

The [headroom/_version.py](https://github.com/chopratejas/headroom/blob/main/headroom/_version.py) module exposes __version__ to applications, preferring source-tree calculations when running from a Git checkout, or falling back to installed package metadata from PyPI for distributed builds.

The Automated Release Pipeline

The versioning logic resides in [headroom/release_version.py](https://github.com/chopratejas/headroom/blob/main/headroom/release_version.py), which implements a five-step deterministic process:

  1. Read the canonical versionget_canonical_version() parses the version = "x.y.z" field from pyproject.toml.
  2. Collect existing tagslist_release_tags() executes git tag -l v* to retrieve all tags matching the vMAJOR.MINOR.PATCH(.N?) pattern.
  3. Select the latest tagfind_latest_release_tag() normalizes historic four-component tags to standard SemVer and selects the highest version.
  4. Determine the bump levellist_release_commits() fetches commits since the previous tag, while determine_bump_level() executes classify_commit_bump() on each commit. The highest required bump (major > minor > patch) wins.
  5. Compute the new versionSemVer.bump() generates the next MAJOR.MINOR.PATCH tuple, wrapped in ReleaseVersionInfo and emitted as GitHub Actions outputs.

# Example: How commit types determine the bump level

from headroom.release_version import classify_commit_bump, CommitInfo

feat = CommitInfo(subject="feat(parser): add new JSON node")
fix = CommitInfo(subject="fix: correct off-by-one")
breaking = CommitInfo(
    subject="refactor!: drop old API",
    body="BREAKING CHANGE: the old `parse()` signature is removed"
)

print(classify_commit_bump(feat))      # → "minor"

print(classify_commit_bump(fix))       # → "patch"

print(classify_commit_bump(breaking))  # → "major"

Handling Legacy Tag Formats

Older Headroom releases occasionally utilized four-part version identifiers (e.g., v0.27.0.3). The normalize_release_tag() helper function strips the extra numeric component, ensuring the automated bump logic operates against the canonical MAJOR.MINOR.PATCH tuple while preserving the original raw tag for changelog generation purposes.

Continuous Delivery Configuration

The repository includes a Release-Please configuration (.release-please-config.json) that automates:

  • Computing the bump level from Conventional Commit messages
  • Publishing Docker images to ghcr.io/chopratejas/headroom with matching semantic versions
  • Updating the manifest (.release-please-manifest.json) that records the current package version

This configuration guarantees that every merge into main triggers a correctly incremented release without requiring manual intervention.

Accessing Version Information at Runtime

Developers can inspect the computed version through multiple interfaces:


# Get the runtime version derived from git tags and commit analysis

import headroom
print(headroom.__version__)

# Output: 0.26.0

# Execute the release-version helper manually for debugging

$ python -m headroom.release_version
version=0.26.0
npm_version=0.26.0
canonical=0.26.0
height=0
bump=patch
previous_tag=v0.25.0

Summary

  • Headroom implements Semantic Versioning automated by Release-Please, analyzing commits since the last tag to determine patch, minor, or major bumps.
  • The canonical version in pyproject.toml serves as the base, while headroom/release_version.py reconciles Git tags and Conventional Commits.
  • Legacy four-part tags (e.g., v0.26.0.1) are normalized to standard SemVer for comparison but preserved in release history.
  • The .release-please-config.json orchestrates continuous delivery, automatically publishing Docker images and updating version manifests.
  • Runtime code accesses versions via headroom.__version__, which intelligently selects between source-tree calculations and installed package metadata.

Frequently Asked Questions

How does Headroom determine the next version number?

Headroom determines the next version by analyzing commits since the last Git tag using the classify_commit_bump() function in headroom/release_version.py. Commits prefixed with feat trigger a minor bump, fix triggers a patch bump, and commits containing BREAKING CHANGE or ending with ! trigger a major bump. The highest precedence bump wins, and SemVer.bump() increments the appropriate component.

What happens if there are no new commits since the last tag?

If no commits exist since the last tag, the height value remains zero and the version returned equals the most recent Git tag. The find_latest_release_tag() function ensures the system never steps backwards to an older version, using the highest normalized tag from the repository history.

How does Headroom handle legacy four-part version tags?

The normalize_release_tag() function in headroom/release_version.py strips the fourth numeric component from legacy tags (e.g., converting v0.27.0.3 to v0.27.0) before performing version comparisons. This ensures the automated SemVer logic functions correctly while the original tag format remains available for changelog and historical reference purposes.

Where is the canonical version defined in the Headroom repository?

The canonical version is defined in the version field within pyproject.toml. This value serves as the base version that the release automation uses before applying commit-derived bumps. The get_canonical_version() function parses this field to establish the starting point for version calculations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →