{"record":{"id":"5c2a8da72c363983","repo":"headroomlabs-ai/headroom","slug":"invalid-semantic-version-value","errorCode":null,"errorMessage":"Invalid semantic version: {value}","messagePattern":"Invalid semantic version: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/release_version.py","lineNumber":58,"sourceCode":"FIELD_SEP = \"\\x1f\"\nRECORD_SEP = \"\\x1e\"\nGIT_LOG_FORMAT = \"%s%x1f%b%x1e\"\nBUMP_PRIORITY = {\"patch\": 0, \"minor\": 1, \"major\": 2}\n\n\n@dataclass(frozen=True, order=True)\nclass SemVer:\n    \"\"\"Semantic version tuple with simple bump helpers.\"\"\"\n\n    major: int\n    minor: int\n    patch: int\n\n    @classmethod\n    def parse(cls, value: str) -> SemVer:\n        match = SEMVER_RE.match(value)\n        if not match:\n            raise ValueError(f\"Invalid semantic version: {value}\")\n        return cls(*(int(part) for part in match.groups()))\n\n    def bump(self, level: str) -> SemVer:\n        if level == \"major\":\n            return SemVer(self.major + 1, 0, 0)\n        if level == \"minor\":\n            return SemVer(self.major, self.minor + 1, 0)\n        if level == \"patch\":\n            return SemVer(self.major, self.minor, self.patch + 1)\n        raise ValueError(f\"Unsupported bump level: {level}\")\n\n    def __str__(self) -> str:\n        return f\"{self.major}.{self.minor}.{self.patch}\"\n\n\n@dataclass(frozen=True)\nclass ReleaseVersionInfo:\n    \"\"\"Workflow outputs for release version calculation.\"\"\"","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/release_version.py#L40-L76","documentation":"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().","triggerScenarios":"Calling SemVer.parse('1.2'), SemVer.parse('v1.2.3') (leading 'v'), SemVer.parse('1.2.3-beta') (prerelease suffix), or SemVer.parse('latest').","commonSituations":"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.","solutions":["Strip the leading 'v' and any build/prerelease suffix before parsing: tag.lstrip('v').split('+')[0].split('-')[0].","For 4-component legacy tags use parse_release_tag()/normalize_release_tag() instead of SemVer.parse().","Validate the tag format in CI before calling the release tooling."],"exampleFix":"# before\nsemver = SemVer.parse(git_tag)  # git_tag = 'v1.2.3'\n\n# after\nsemver = SemVer.parse(git_tag.lstrip('v'))","handlingStrategy":"validation","validationCode":"import re\nSEMVER = re.compile(r'^\\d+\\.\\d+\\.\\d+$')\ntag = git_tag.lstrip('v')\nif not SEMVER.match(tag):\n    raise ValueError(f'not a plain X.Y.Z version: {git_tag!r}')\nsemver = SemVer.parse(tag)","typeGuard":"import re\n\ndef is_plain_semver(value: str) -> bool:\n    return re.match(r'^\\d+\\.\\d+\\.\\d+$', value) is not None","tryCatchPattern":"try:\n    semver = SemVer.parse(tag.lstrip('v'))\nexcept ValueError as e:\n    raise ReleaseError(f'unparseable tag {tag!r}: {e}') from e","preventionTips":["Strip the 'v' prefix from git tags before parsing.","Route 4-component legacy tags through parse_release_tag(), not SemVer.parse().","Enforce a tag naming convention in CI (protected tag rules)."],"tags":["versioning","release","validation"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}