AlexsJones/llmfit · error · ValueError
Invalid version
Error message
Invalid version: {version!r} What it means
Raised by the LlmfitMetadataHook in hatch_build.py while Hatchling resolves package metadata. The version is taken from the LLMFIT_VERSION environment variable or the [workspace.package] version field of the root Cargo.toml, and it must match the strict regex ^\d+\.\d+\.\d+$. Any deviation from bare X.Y.Z — pre-release suffix, build metadata, or a leading 'v' — is rejected with this ValueError before the wheel/sdist metadata is set.
Solutions
- Set [workspace.package].version in Cargo.toml to a plain 'X.Y.Z' string (e.g. '0.9.8') with no prefix or suffix
- Check the environment: `echo $LLMFIT_VERSION` — if it carries a prefix/suffix, export a clean value or `unset LLMFIT_VERSION` so Cargo.toml is used
- If pre-release versions must be supported, relax the guard in hatch_build.py to r"^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$" and normalize to PEP 440 before assigning metadata['version']
Example fix
# before (hatch_build.py)
if not re.match(r"^\d+\.\d+\.\d+$", version):
raise ValueError(f"Invalid version: {version!r}")
# after — allow a pre-release/build suffix
if not re.match(r"^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$", version):
raise ValueError(f"Invalid version: {version!r}") Defensive patterns
Strategy: validation
Validate before calling
import os, re, tomllib
version = os.environ.get("LLMFIT_VERSION") or tomllib.load(open("Cargo.toml", "rb"))["workspace"]["package"]["version"]
if not re.fullmatch(r"\d+\.\d+\.\d+", version):
raise SystemExit(f"LLMFIT_VERSION/Cargo.toml version {version!r} is not plain X.Y.Z — fix before building") Try / catch
try:
subprocess.run(["uv", "build"], check=True)
except subprocess.CalledProcessError as e:
# hatchling surfaces the hook's ValueError in stderr; fail the release job early
raise SystemExit(f"wheel build failed (check version string): {e}") Prevention
- Keep [workspace.package].version in Cargo.toml as bare semver; never commit pre-release strings
- In CI, validate LLMFIT_VERSION with a regex step before invoking the build
- Derive LLMFIT_VERSION from a tag with 'v' stripped and suffixes removed
When it happens
Trigger: Running `uv build`, `hatch build`, or `pip install -e llmfit-python` when: (1) LLMFIT_VERSION is exported as something like '0.9.8-rc1', 'v0.9.8', or '0.10'; (2) Cargo.toml's [workspace.package] version was bumped to a pre-release such as '1.0.0-beta.1'; (3) CI writes a git tag (with a 'v' prefix or suffix) into LLMFIT_VERSION verbatim.
Common situations: Cutting a release candidate, tagging with 'v' prefixes, or a CI job that derives LLMFIT_VERSION from `git describe` output. Also happens after manually editing Cargo.toml and leaving stray characters in the version string.
Related errors
- Unknown version
- Binary not found at . The binary selection logic selected a…
- Binary version mismatch: binary at
- llmfit binary not found at
- LLMFIT_PYTHON_PLATFORM_TAG is not supported for editable…
AI-assisted analysis of AlexsJones/llmfit@acc7e40c3a (2026-08-17).
Data as JSON: /api/errors/ae200507b967eafb.
Report an issue: GitHub.
Appendix: source
Thrown at llmfit-python/hatch_build.py:74
PLUGIN_NAME = "llmfit version, license and readme"
def update(self, metadata: dict) -> None:
"""Populate dynamic metadata from the repository.
``version`` and ``license-expression`` come from ``Cargo.toml``,
``readme`` from the repository root.
Version resolution order:
1. ``LLMFIT_VERSION`` environment variable (e.g. ``0.9.8``).
2. The ``version`` field in ``[workspace.package]`` from ``Cargo.toml``.
"""
with (Path(self.root).parent / "Cargo.toml").open("rb") as f:
workspace_package: dict[str, str] = tomli.load(f)["workspace"]["package"]
version: str = os.environ.get("LLMFIT_VERSION") or workspace_package["version"]
if not re.match(r"^\d+\.\d+\.\d+$", version):
raise ValueError(f"Invalid version: {version!r}")
metadata["version"] = version
metadata["license-expression"] = workspace_package["license"]
# The package README is the repository one. Hatchling refuses a
# `readme = "../README.md"` path ("must be within the project
# directory") but takes the contents verbatim, so read it here rather
# than duplicating or symlinking the file.
readme = Path(self.root).parent / "README.md"
metadata["readme"] = {
"content-type": "text/markdown",
"text": readme.read_text(encoding="utf-8"),
}
class LlmfitBinaryBuildHook(BuildHookInterface):
"""Hatchling build hook that injects the llmfit binary into each wheel."""
PLUGIN_NAME = "llmfit binary"View on GitHub (pinned to acc7e40c3a)