Derive the distribution version from git tags#275
Conversation
pyproject carried `version = "0.0.1"` while `v0.0.2` was the live tag — the exact drift a hand-maintained version number invites. Switch to uv-dynamic-versioning: `project.dynamic = ["version"]`, resolved from the latest `v*` tag, so `git tag vX.Y.Z && git push --tags` is the whole release action and there is no second number left to disagree with it. Dunamai's default pattern requires the `v` prefix, which is what keeps the docs-deploy CalVer tags (2026.WW.N, disjoint from library versions by design) from being read as releases. `bump = true` versions between-tag builds as `<next>.devN+<commit>` — a local segment PyPI rejects, so only a tag can produce an uploadable version. Unpacked sdists resolve from their own PKG-INFO, so `pip install xy-X.Y.Z.tar.gz` needs no repo. The load-bearing part is `fetch-depth: 0` on every checkout. actions/checkout defaults to a depth-1 clone carrying no tags, under which the version resolves to the 0.0.0 fallback *silently* — a build succeeds and ships the wrong number rather than failing, invisible until someone reads a PyPI page. verify_ci_workflow now rejects any workflow that omits it. The docs backend image is the one build that legitimately has no git metadata (.dockerignore excludes .git), so CI resolves the version on the runner and passes it in explicitly. Ripple: - `xy.__version__` reads installed distribution metadata instead of a hardcoded string, resolved lazily through the existing `__getattr__` so importlib.metadata stays out of the import-time budget (measured: import xy at 3.5 ms of 200). - verify_wheel/verify_sdist check each artifact against itself — filename vs .dist-info vs METADATA, sdist root vs PKG-INFO — rather than reading a pyproject version that no longer exists. The wheel gains a .dist-info name check the old comparison did not cover. - The release gate drops its tag-vs-pyproject leg (now tautological) and keeps tag vs CHANGELOG, plus a check that the tag is shaped like a release tag. - uv omits the version for dynamic-versioned editable packages, so `--frozen` syncs do not churn per commit.
📝 WalkthroughWalkthroughChangesVersioning and release pipeline
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant GitTag
participant GitHubActions
participant DynamicVersioning
participant ReleaseGate
participant Artifact
GitTag->>GitHubActions: trigger release workflow
GitHubActions->>DynamicVersioning: provide full history and tags
DynamicVersioning->>Artifact: derive distribution version
GitHubActions->>ReleaseGate: validate tag and CHANGELOG.md
ReleaseGate-->>GitHubActions: allow or reject publication
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
scripts/verify_ci_workflow.py (1)
258-258: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winValidate the docs workflow’s checkout depth too.
.github/workflows/docs.ymlbuilds the editable package, but this checker only enforcesfetch-depth: 0for CI, CodSpeed, and release. Add docs-workflow validation and a shallow-checkout regression test so its dynamic version cannot silently regress.Also applies to: 622-622, 668-668
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/verify_ci_workflow.py` at line 258, Update the workflow verification logic around _require_unshallow_checkouts to validate the docs workflow in addition to CI, CodSpeed, and release workflows, ensuring docs.yml requires fetch-depth: 0. Add or extend the shallow-checkout regression test to cover the docs workflow and its dynamic-version build path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci.yml:
- Around line 25-28: Disable persisted checkout credentials by adding
persist-credentials: false to every actions/checkout step alongside fetch-depth:
0: .github/workflows/ci.yml lines 25-28, 61-64, 234-237, 266-269, 370-373,
447-450, 548-551, 603-606, 667-670, and 741-744; .github/workflows/codspeed.yml
lines 28-31; and .github/workflows/docs.yml lines 47-50 and 86-89.
---
Nitpick comments:
In `@scripts/verify_ci_workflow.py`:
- Line 258: Update the workflow verification logic around
_require_unshallow_checkouts to validate the docs workflow in addition to CI,
CodSpeed, and release workflows, ensuring docs.yml requires fetch-depth: 0. Add
or extend the shallow-checkout regression test to cover the docs workflow and
its dynamic-version build path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 09dd2ee7-b606-4cdf-aa21-1d3a14359e53
⛔ Files ignored due to path filters (2)
docs/app/uv.lockis excluded by!**/*.lockuv.lockis excluded by!**/*.lock
📒 Files selected for processing (19)
.github/workflows/_build-docs-images.yml.github/workflows/benchmark-refresh.yml.github/workflows/ci.yml.github/workflows/codspeed.yml.github/workflows/docs.yml.github/workflows/release.ymlCHANGELOG.mddocs/app/dockerfiles/backend.Dockerfilepyproject.tomlpython/xy/__init__.pyscripts/check_public_api.pyscripts/check_release_version.pyscripts/verify_ci_workflow.pyscripts/verify_sdist.pyscripts/verify_wheel.pyspec/process/production-readiness.mdtests/test_check_release_version.pytests/test_public_api.pytests/test_verify_ci_workflow.py
| with: | ||
| # Full history and tags: the distribution version is derived | ||
| # from the latest `v*` tag, and a shallow clone has none. | ||
| fetch-depth: 0 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
set -euo pipefail
for f in .github/workflows/ci.yml .github/workflows/codspeed.yml .github/workflows/docs.yml; do
echo "### $f"
wc -l "$f"
done
echo "### checkout/persist/fetch-depth occurrences"
rg -n -C 2 'actions/checkout|persist-credentials|fetch-depth' .github/workflows/ci.yml .github/workflows/codspeed.yml .github/workflows/docs.ymlRepository: reflex-dev/xy
Length of output: 9457
🏁 Script executed:
set -euo pipefail
echo "### .github/workflows/ci.yml"
sed -n '1,90p' .github/workflows/ci.yml
echo
sed -n '220,285p' .github/workflows/ci.yml
echo
sed -n '360,385p' .github/workflows/ci.yml
echo
sed -n '440,460p' .github/workflows/ci.yml
echo
sed -n '540,560p' .github/workflows/ci.yml
echo
sed -n '595,615p' .github/workflows/ci.yml
echo
sed -n '660,680p' .github/workflows/ci.yml
echo
sed -n '735,755p' .github/workflows/ci.yml
echo "### .github/workflows/codspeed.yml"
sed -n '1,60p' .github/workflows/codspeed.yml
echo "### .github/workflows/docs.yml"
sed -n '1,110p' .github/workflows/docs.ymlRepository: reflex-dev/xy
Length of output: 17788
Disable persisted checkout credentials. These actions/checkout steps keep the repo token in git config by default; add persist-credentials: false alongside fetch-depth: 0 in each checkout under .github/workflows/ci.yml, .github/workflows/codspeed.yml, and .github/workflows/docs.yml.
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 24-28: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
📍 Affects 3 files
.github/workflows/ci.yml#L25-L28(this comment).github/workflows/ci.yml#L61-L64.github/workflows/ci.yml#L234-L237.github/workflows/ci.yml#L266-L269.github/workflows/ci.yml#L370-L373.github/workflows/ci.yml#L447-L450.github/workflows/ci.yml#L548-L551.github/workflows/ci.yml#L603-L606.github/workflows/ci.yml#L667-L670.github/workflows/ci.yml#L741-L744.github/workflows/codspeed.yml#L28-L31.github/workflows/docs.yml#L47-L50.github/workflows/docs.yml#L86-L89
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml around lines 25 - 28, Disable persisted checkout
credentials by adding persist-credentials: false to every actions/checkout step
alongside fetch-depth: 0: .github/workflows/ci.yml lines 25-28, 61-64, 234-237,
266-269, 370-373, 447-450, 548-551, 603-606, 667-670, and 741-744;
.github/workflows/codspeed.yml lines 28-31; and .github/workflows/docs.yml lines
47-50 and 86-89.
Source: Linters/SAST tools
What
pyproject.tomlswitches from a staticversion = "0.0.1"todynamic = ["version"], resolved from the latestv*git tag by theuv-dynamic-versioninghatchling plugin.
Motivating drift: pyproject said
0.0.1whilev0.0.2was the live tag. Afterthis,
git tag vX.Y.Z && git push --tagsis the whole release action — there isno second number left to disagree with the tag.
Verified, not assumed
git describeonmainreturns2026.30.1(a docs-deploy tag, disjoint from library versions by design), butdunamai's default pattern requires the
vprefix, so the plugin resolves0.0.2. No custom pattern needed.0.0.3.dev1+5fc6676(bump = true) — alocal segment PyPI rejects, so only a tag produces an uploadable version.
PKG-INFOin an unpackedsdist; mangling it to
9.9.9produced a9.9.9wheel. Sopip install xy-X.Y.Z.tar.gzkeeps the published version.xy.__version__ == "0.0.2",import xyat3.5 ms against the 200 ms budget, with
importlib.metadatanot eagerlyimported.
The load-bearing part:
fetch-depth: 0actions/checkoutdefaults to a depth-1 clone carrying no tags, under which theversion resolves to the
0.0.0fallback silently — the build succeeds andships the wrong number rather than failing, invisible until someone reads a PyPI
page. This applies to the tag push that publishes to PyPI.
All 21 checkouts now set
fetch-depth: 0, andverify_ci_workflow.pyrejectsany workflow that omits it (negative-tested: fires on a shallow checkout, passes
on a deep one).
The docs backend image is the one build that legitimately has no git metadata
(
.dockerignoreexcludes.git), so CI resolves the version on the runner andpasses it in via
UV_DYNAMIC_VERSIONING_BYPASS.Ripple
xy.__version__reads installed distribution metadata instead of a hardcodedstring, resolved lazily through the existing
__getattr__.verify_wheel.py/verify_sdist.pycheck each artifact against itself(filename ↔
.dist-info↔METADATA; sdist root ↔PKG-INFO) rather thanreading a pyproject version that no longer exists. The wheel gains a
.dist-infoname check the old comparison did not cover.tag ↔ CHANGELOG, and adds a check that the tag is shaped like a release tag.
uvomits the version for dynamic-versioned editable packages, so--frozensyncs do not churn per commit. Both lockfiles pass
uv lock --check.Test evidence
check_public_api,verify_ci_workflow,ruff check,ruff format --check: clean0.0.2Note for whoever cuts the next release
CHANGELOG.mdhas no dated## [0.0.2]entry — only0.0.1and[Unreleased]. That predates this PR, but the release gate will now block thenext tag until a dated heading exists for it.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation