There's a special kind of bug that no test suite can catch, because the test suite never looks at it: the release itself.
Here's a real one — from June 2026, fixed upstream the following month. google-adk==1.36.0 was installable from PyPI, but GitHub had no release or tag for v1.36.0: the releases page returned 404 and git ls-remote --tags showed only v1.36.1. (The maintainer created the missing release on 2026-07-07, so this is a historical incident — and a good one, because it shows exactly what slips through.)
Why does a missing tag matter? Because downstream release watchers, security scanners, and anyone bisecting a regression six months later all assume "PyPI version X" and "git tag X" are two names for the same thing. When they silently diverge, nobody's CI notices. Your tests pass, your wheel builds, twine uploads fine — and the thing your users actually install has no corresponding tag in your repo.
And the google-adk story had a second, subtler half: the v1.36.1 tag pointed at a commit whose version.py still said __version__ = "1.36.0", while the PyPI 1.36.1 sdist said 1.36.1. The tag didn't match the code that shipped. That half is important for honesty: tagtruth v0.1.0 detects the first failure mode (missing/misnamed tags), not the second (tag pointing at the wrong commit). The first is what the tool ships; the second is what it revealed as the next check to build.
The missing check
This is the fourth tool in my release-integrity series — the question each one asks is "the release passed, but what actually shipped?"
- readmeta: did your README render on PyPI, or are the images broken?
- wheeltruth: did your wheel ship complete, or are files missing?
- tagtruth: do your PyPI versions actually have matching Git tags?
tagtruth is a zero-dependency Python CLI. Point it at a package and its repo:
pip install tagtruth
tagtruth check --package google-adk --repo google/adk-python
version expected tag found tag sdist verdict
------- -------------- --------- ----- -------
1.36.1 v1.36.1 v1.36.1 - OK
1.36.0 v1.36.0/1.36.0 - - MISSING_TAG
checked 2 versions, 1 mismatch(es): 1.36.0 (MISSING_TAG)
(The example above replays the historical incident from pinned fixtures — the live repo has since been fixed, so running it today against google-adk reports clean.)
Exit code 1 on mismatches, 0 when clean, 2 on errors — so it drops straight into CI as a post-release job.
The deep check
--deep goes one level further: it downloads each version's sdist and reads the Version: field out of PKG-INFO, then compares it against both the PyPI version and the tag name. That catches two more failure modes:
- SDIST_MISMATCH: PyPI lists version X, but the sdist's own metadata says Y.
- TAG_SDIST_MISMATCH: the tag name disagrees with what the sdist claims to be.
tagtruth check --package my-pkg --repo myorg/myrepo --all --deep
False positives are the real enemy
Here's the thing I learned building this: a naive version of this tool is useless. Tons of projects intentionally skip tags for some releases, or name their tags nothing like their versions. A checker that screams about every one of those isn't a tool, it's a pager going off at 3am.
So version mapping is a first-class feature, not an afterthought. In your pyproject.toml:
[tool.tagtruth]
ignore = ["0.0.0-nightly", "1.0.0rc1"]
map = { "1.36.0" = "v1.36", "2.0" = "release-2.0" }
Or ad hoc: tagtruth check --package x --repo y/z --map 1.36.0=v1.36 --ignore 0.0.0-nightly. CLI flags override the file per key.
What tagtruth does not verify yet
Being precise about the boundary: v0.1.0 checks that tag names match. --deep compares the sdist's PKG-INFO against the PyPI version and the tag name. It does not check what commit a tag points at — so a passing check means the identifiers line up, not that the package was actually built from the tagged source. Commit-content comparison is the next check on the roadmap; the deep-checker is structured so it can slot in.
One more thing I fixed while writing this: the deep check used to report "couldn't verify" as a pass. A failed verification is now its own verdict (NO_SDIST / CHECK_FAILED) and exits non-zero — because "I couldn't check" must never look like "it's fine."
CI snippet
- name: Check release/tag integrity
run: |
pip install tagtruth
tagtruth check --package my-pkg --repo myorg/myrepo --all
The release passed. Now check what actually shipped: github.com/hahahahahahahahah6/tagtruth
Top comments (0)