headroomlabs-ai/headroom · error · ValueError

Invalid semantic version: {value}

Error message

Invalid semantic version: {value}

What it means

Raised by SemVer.parse() when the input string does not match the semantic version regex (major.minor.patch of non-negative integers). The release-version tooling uses strict parsing so that malformed tags never silently produce a wrong bump. Note it accepts plain X.Y.Z only; 4-component legacy tags must go through parse_release_tag().

Source

Thrown at headroom/release_version.py:58

FIELD_SEP = "\x1f"
RECORD_SEP = "\x1e"
GIT_LOG_FORMAT = "%s%x1f%b%x1e"
BUMP_PRIORITY = {"patch": 0, "minor": 1, "major": 2}


@dataclass(frozen=True, order=True)
class SemVer:
    """Semantic version tuple with simple bump helpers."""

    major: int
    minor: int
    patch: int

    @classmethod
    def parse(cls, value: str) -> SemVer:
        match = SEMVER_RE.match(value)
        if not match:
            raise ValueError(f"Invalid semantic version: {value}")
        return cls(*(int(part) for part in match.groups()))

    def bump(self, level: str) -> SemVer:
        if level == "major":
            return SemVer(self.major + 1, 0, 0)
        if level == "minor":
            return SemVer(self.major, self.minor + 1, 0)
        if level == "patch":
            return SemVer(self.major, self.minor, self.patch + 1)
        raise ValueError(f"Unsupported bump level: {level}")

    def __str__(self) -> str:
        return f"{self.major}.{self.minor}.{self.patch}"


@dataclass(frozen=True)
class ReleaseVersionInfo:
    """Workflow outputs for release version calculation."""

View on GitHub (pinned to 322425c43b)

Solutions

  1. Strip the leading 'v' and any build/prerelease suffix before parsing: tag.lstrip('v').split('+')[0].split('-')[0].
  2. For 4-component legacy tags use parse_release_tag()/normalize_release_tag() instead of SemVer.parse().
  3. Validate the tag format in CI before calling the release tooling.

Example fix

# before
semver = SemVer.parse(git_tag)  # git_tag = 'v1.2.3'

# after
semver = SemVer.parse(git_tag.lstrip('v'))
Defensive patterns

Strategy: validation

Validate before calling

import re
SEMVER = re.compile(r'^\d+\.\d+\.\d+$')
tag = git_tag.lstrip('v')
if not SEMVER.match(tag):
    raise ValueError(f'not a plain X.Y.Z version: {git_tag!r}')
semver = SemVer.parse(tag)

Type guard

import re

def is_plain_semver(value: str) -> bool:
    return re.match(r'^\d+\.\d+\.\d+$', value) is not None

Try / catch

try:
    semver = SemVer.parse(tag.lstrip('v'))
except ValueError as e:
    raise ReleaseError(f'unparseable tag {tag!r}: {e}') from e

Prevention

When it happens

Trigger: Calling SemVer.parse('1.2'), SemVer.parse('v1.2.3') (leading 'v'), SemVer.parse('1.2.3-beta') (prerelease suffix), or SemVer.parse('latest').

Common situations: Passing git tag refs like 'v1.2.3' without stripping the prefix; feeding legacy 4-part tags ('1.2.3.4') to the wrong parser; passing branch names or 'latest' aliases from a CI variable.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/5c2a8da72c363983. Report an issue: GitHub.