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

> Discover Headroom's automated semantic versioning strategy. Learn how Release-Please and Conventional Commits simplify version management for your project.

- Repository: [Tejas Chopra/headroom](https://github.com/chopratejas/headroom)
- Tags: best-practices
- Published: 2026-06-21

---

**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`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/pyproject.toml) | [[`pyproject.toml`](https://github.com/chopratejas/headroom/blob/main/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.py`](https://github.com/chopratejas/headroom/blob/main/headroom/release_version.py) → `find_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.py`](https://github.com/chopratejas/headroom/blob/main/headroom/release_version.py) → `classify_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)](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)](https://github.com/chopratejas/headroom/blob/main/headroom/release_version.py), which implements a five-step deterministic process:

1. **Read the canonical version** – `get_canonical_version()` parses the `version = "x.y.z"` field from [`pyproject.toml`](https://github.com/chopratejas/headroom/blob/main/pyproject.toml).
2. **Collect existing tags** – `list_release_tags()` executes `git tag -l v*` to retrieve all tags matching the `vMAJOR.MINOR.PATCH(.N?)` pattern.
3. **Select the latest tag** – `find_latest_release_tag()` normalizes historic four-component tags to standard SemVer and selects the highest version.
4. **Determine the bump level** – `list_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 version** – `SemVer.bump()` generates the next `MAJOR.MINOR.PATCH` tuple, wrapped in `ReleaseVersionInfo` and emitted as GitHub Actions outputs.

```python

# 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`](https://github.com/chopratejas/headroom/blob/main/.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`](https://github.com/chopratejas/headroom/blob/main/.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:

```python

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

import headroom
print(headroom.__version__)

# Output: 0.26.0

```

```bash

# 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`](https://github.com/chopratejas/headroom/blob/main/pyproject.toml) serves as the base, while [`headroom/release_version.py`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/.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`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/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`](https://github.com/chopratejas/headroom/blob/main/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.