{"record":{"id":"3b78f17f9a9e89c9","repo":"headroomlabs-ai/headroom","slug":"invalid-release-tag-tag","errorCode":null,"errorMessage":"Invalid release tag: {tag}","messagePattern":"Invalid release tag: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/release_version.py","lineNumber":118,"sourceCode":"    version: SemVer\n    legacy_height: int = -1\n    raw: str = \"\"\n\n\n@dataclass(frozen=True)\nclass CommitInfo:\n    \"\"\"Commit subject/body pair used for bump detection.\"\"\"\n\n    subject: str\n    body: str = \"\"\n\n\ndef parse_release_tag(tag: str) -> ReleaseTag:\n    \"\"\"Parse a release tag, preserving legacy fourth-component ordering.\"\"\"\n\n    match = RELEASE_TAG_RE.match(tag)\n    if not match:\n        raise ValueError(f\"Invalid release tag: {tag}\")\n    major, minor, patch, extra = match.groups()\n    return ReleaseTag(\n        version=SemVer(int(major), int(minor), int(patch)),\n        legacy_height=int(extra) if extra is not None else -1,\n        raw=tag,\n    )\n\n\ndef normalize_release_tag(tag: str) -> SemVer:\n    \"\"\"Collapse historic 4-part release tags into their base semantic version.\"\"\"\n\n    return parse_release_tag(tag).version\n\n\ndef find_latest_release_tag(tags: Sequence[str]) -> str | None:\n    \"\"\"Return the latest release tag after normalizing legacy 4-part tags.\"\"\"\n\n    candidates: list[ReleaseTag] = []","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/release_version.py#L100-L136","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Extract the numeric portion before parsing, e.g. strip a known prefix with a regex.","Pass only actual release tags (X.Y.Z or X.Y.Z.N) to parse_release_tag().","Normalize tags once in CI (e.g. ${GITHUB_REF_NAME#v}) before calling release tooling."],"exampleFix":"# before\ntag_info = parse_release_tag(github_ref)  # 'v1.2.3'\n\n# after\ntag_info = parse_release_tag(github_ref.removeprefix('v'))","handlingStrategy":"validation","validationCode":"import re\nRELEASE_TAG = re.compile(r'^\\d+\\.\\d+\\.\\d+(?:\\.\\d+)?$')\nif not RELEASE_TAG.match(tag):\n    raise ValueError(f'not a release tag: {tag!r}')\ninfo = parse_release_tag(tag)","typeGuard":"import re\n\ndef is_release_tag(tag: str) -> bool:\n    return re.match(r'^\\d+\\.\\d+\\.\\d+(?:\\.\\d+)?$', tag) is not None","tryCatchPattern":"try:\n    info = parse_release_tag(tag.removeprefix('v').strip())\nexcept ValueError as e:\n    raise ReleaseError(f'bad tag {tag!r}: {e}') from e","preventionTips":["Normalize refs once in CI (strip prefixes, whitespace) before release tooling sees them.","Only pass tag refs to parse_release_tag(), never branch names or describe output.","Standardize tag format (optionally 'v'-prefixed) across the repository."],"tags":["versioning","release","validation","git"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}