headroomlabs-ai/headroom · error · ValueError

Invalid release tag: {tag}

Error message

Invalid release tag: {tag}

What it means

Raised by parse_release_tag() when the tag does not match the release tag regex (X.Y.Z with an optional legacy fourth numeric component). This parser exists specifically to accept historic 4-part tags and record their ordering via legacy_height; strings outside that shape are rejected.

Source

Thrown at headroom/release_version.py:118

    version: SemVer
    legacy_height: int = -1
    raw: str = ""


@dataclass(frozen=True)
class CommitInfo:
    """Commit subject/body pair used for bump detection."""

    subject: str
    body: str = ""


def parse_release_tag(tag: str) -> ReleaseTag:
    """Parse a release tag, preserving legacy fourth-component ordering."""

    match = RELEASE_TAG_RE.match(tag)
    if not match:
        raise ValueError(f"Invalid release tag: {tag}")
    major, minor, patch, extra = match.groups()
    return ReleaseTag(
        version=SemVer(int(major), int(minor), int(patch)),
        legacy_height=int(extra) if extra is not None else -1,
        raw=tag,
    )


def normalize_release_tag(tag: str) -> SemVer:
    """Collapse historic 4-part release tags into their base semantic version."""

    return parse_release_tag(tag).version


def find_latest_release_tag(tags: Sequence[str]) -> str | None:
    """Return the latest release tag after normalizing legacy 4-part tags."""

    candidates: list[ReleaseTag] = []

View on GitHub (pinned to 322425c43b)

Solutions

  1. Extract the numeric portion before parsing, e.g. strip a known prefix with a regex.
  2. Pass only actual release tags (X.Y.Z or X.Y.Z.N) to parse_release_tag().
  3. Normalize tags once in CI (e.g. ${GITHUB_REF_NAME#v}) before calling release tooling.

Example fix

# before
tag_info = parse_release_tag(github_ref)  # 'v1.2.3'

# after
tag_info = parse_release_tag(github_ref.removeprefix('v'))
Defensive patterns

Strategy: validation

Validate before calling

import re
RELEASE_TAG = re.compile(r'^\d+\.\d+\.\d+(?:\.\d+)?$')
if not RELEASE_TAG.match(tag):
    raise ValueError(f'not a release tag: {tag!r}')
info = parse_release_tag(tag)

Type guard

import re

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

Try / catch

try:
    info = parse_release_tag(tag.removeprefix('v').strip())
except ValueError as e:
    raise ReleaseError(f'bad tag {tag!r}: {e}') from e

Prevention

When it happens

Trigger: Calling parse_release_tag('v1.2.3'), parse_release_tag('release-1.2.3'), parse_release_tag('1.2'), or passing a branch/ref name instead of a tag.

Common situations: Repository tags carry a prefix ('v' or 'release-') while the parser expects bare numeric tags; sorting mixed refs; trailing whitespace or glob patterns from 'git describe' output.

Related errors


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