From e28581faa44c4f4289c4da53027541155d84ee06 Mon Sep 17 00:00:00 2001 From: Joe Petrucelli Date: Mon, 3 Aug 2026 20:25:23 -0400 Subject: [PATCH] fix(version): derive __version__ from package metadata, guard the drift The 0.1.1 release bumped the literal in pyproject.toml and left the second one in claude_batch_runner/__init__.py at 0.1.0, so the package attribute reported the previous release for the whole of 0.1.1. Rather than bump the second literal and re-arm the same trap, remove it: __version__ now derives from installed metadata, leaving pyproject.toml as the single source of truth -- which is what release.yml already cross-checks against the git tag. tests/test_version.py fails if the two disagree, which also catches a stale editable install. A never-installed source checkout reports 0.0.0.dev0 rather than a fictional release number. --- CHANGELOG.md | 11 +++++++++++ claude_batch_runner/__init__.py | 11 ++++++++++- tests/test_version.py | 35 +++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/test_version.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 28a09a9..55b3ec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Fixed + +- **`__version__` reported `0.1.0` for the whole of `0.1.1`.** The release + bumped the literal in `pyproject.toml` and left the second one in + `claude_batch_runner/__init__.py` behind, so anything reading the package + attribute saw the previous release. `__version__` is now *derived* from + installed metadata, leaving `pyproject.toml` as the single source of truth + (which is what `release.yml` already cross-checks against the git tag), and + `tests/test_version.py` fails if the two ever disagree again. A source + checkout that was never pip-installed reports `0.0.0.dev0`. + ### Added - **`cwd` and `permission_mode` on `call_agent`** — two optional keyword-only diff --git a/claude_batch_runner/__init__.py b/claude_batch_runner/__init__.py index b686ff3..925c41d 100644 --- a/claude_batch_runner/__init__.py +++ b/claude_batch_runner/__init__.py @@ -10,6 +10,9 @@ Public API is re-exported here. """ +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as _installed_version + from claude_batch_runner.driver import ( DEFAULT_AGENT_TIMEOUT_S, AgentError, @@ -52,4 +55,10 @@ "verify_unit", ] -__version__ = "0.1.0" +# Derived, never written by hand. `pyproject.toml` is the single source of +# truth — release.yml already cross-checks it against the git tag — and a +# second literal here drifted the moment 0.1.1 bumped one and not the other. +try: + __version__ = _installed_version("claude-batch-runner") +except PackageNotFoundError: # source checkout, never pip-installed + __version__ = "0.0.0.dev0" diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..1cc9911 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,35 @@ +"""`__version__` agrees with pyproject.toml. Never spawns a real `claude` process.""" + +from __future__ import annotations + +import tomllib +from pathlib import Path + +import pytest + +import claude_batch_runner + +REPO_ROOT = Path(__file__).resolve().parents[1] +PYPROJECT = REPO_ROOT / "pyproject.toml" + + +def _declared_version() -> str: + with PYPROJECT.open("rb") as fh: + return tomllib.load(fh)["project"]["version"] + + +def test_version_matches_pyproject(): + """0.1.1 bumped pyproject and left `__version__` at 0.1.0 for a whole release. + + `__version__` is now derived from installed metadata rather than a second + literal, so the only way this can disagree is a stale editable install -- + which is itself worth failing on, because every other check in the repo + (release.yml's tag cross-check included) trusts pyproject. + """ + assert claude_batch_runner.__version__ == _declared_version() + + +def test_version_is_not_the_uninstalled_fallback(): + """The PackageNotFoundError branch must not be what the suite exercises.""" + if claude_batch_runner.__version__ == "0.0.0.dev0": + pytest.fail("package is not installed; run `pip install -e .[dev]` before pytest")