The 0.10.0 release failed on its first run and the error pointed at the wrong thing: https://github.com/CodeGateSoftware/keel/actions/runs/32316126013
What happened
#422 bumped the version in all seven pyproject.toml files and missed uv.lock, which is tracked. The workflow's "Sync dependencies" step regenerated it, leaving the checkout dirty. "Stamp build info" then stamped from that dirty checkout, and five steps later:
warning: this build is NOT reproducible -- it does not correspond to a commit. Do not run it against live funds.
keel 0.10.0+29464f06ee11 (DIRTY) [release]
::error::artifact reports a dirty tree
Fixed by #423, one uv lock.
Why this is worth a guard rather than a lesson
The failure is correct but the message is misdirecting. "artifact reports a dirty tree" sends a reader to keel/version.py, to _build_info.py, to the stamp step — everywhere except the lockfile, which is the actual cause and is three steps upstream. Nothing in the log says "uv.lock changed". Diagnosing it meant reading version.build_info(), confirming keel/_build_info.py is gitignored so it could not be the culprit, and only then checking whether the lock was tracked and stale.
It costs a full build to find out. Lint, Type-check, Test and Build all ran and passed — several minutes — before the guard fired. A lockfile check is instant.
The class of mistake recurs. Every release bumps seven pyproject.toml files, and the lockfile is the eighth thing that has to move with them. It is not mentioned in RELEASING.md's "Cutting a release" steps, which say only "edit version in pyproject.toml".
Proposal
Add a step immediately after checkout, before anything mutates the tree:
- name: The lockfile must already be current
run: uv lock --check
uv lock --check exits non-zero when the lockfile is out of date with the manifests, and names the lockfile. It runs in about a second.
Worth doing in two more places:
- the same check in the ordinary CI workflow, so a version-bump PR fails before it merges rather than at release time;
- a line in
RELEASING.md step 1: the bump is pyproject.toml and uv lock.
Not proposed
Having the workflow run uv lock itself and commit it. RELEASING.md is explicit that CI never writes to main and that the version decision belongs in a reviewed PR; a workflow that quietly fixed the lockfile would be the same principle violated from a different direction.
The
0.10.0release failed on its first run and the error pointed at the wrong thing: https://github.com/CodeGateSoftware/keel/actions/runs/32316126013What happened
#422 bumped the version in all seven
pyproject.tomlfiles and misseduv.lock, which is tracked. The workflow's "Sync dependencies" step regenerated it, leaving the checkout dirty. "Stamp build info" then stamped from that dirty checkout, and five steps later:Fixed by #423, one
uv lock.Why this is worth a guard rather than a lesson
The failure is correct but the message is misdirecting. "artifact reports a dirty tree" sends a reader to
keel/version.py, to_build_info.py, to the stamp step — everywhere except the lockfile, which is the actual cause and is three steps upstream. Nothing in the log says "uv.lock changed". Diagnosing it meant readingversion.build_info(), confirmingkeel/_build_info.pyis gitignored so it could not be the culprit, and only then checking whether the lock was tracked and stale.It costs a full build to find out. Lint, Type-check, Test and Build all ran and passed — several minutes — before the guard fired. A lockfile check is instant.
The class of mistake recurs. Every release bumps seven
pyproject.tomlfiles, and the lockfile is the eighth thing that has to move with them. It is not mentioned inRELEASING.md's "Cutting a release" steps, which say only "editversioninpyproject.toml".Proposal
Add a step immediately after checkout, before anything mutates the tree:
uv lock --checkexits non-zero when the lockfile is out of date with the manifests, and names the lockfile. It runs in about a second.Worth doing in two more places:
RELEASING.mdstep 1: the bump ispyproject.tomlanduv lock.Not proposed
Having the workflow run
uv lockitself and commit it.RELEASING.mdis explicit that CI never writes tomainand that the version decision belongs in a reviewed PR; a workflow that quietly fixed the lockfile would be the same principle violated from a different direction.