ci: verify the wheel installs before publishing it - #38
Merged
Conversation
release.yml built a wheel, ran twine check on it, and uploaded it straight to PyPI without ever installing it. The job that does install a wheel and import every module out of it lives in scheduled-ci.yml and runs weekly, so it answers 'did the world break rigout since our last commit' - a real question, and not the one that matters at release time, where a wrong answer is permanent because PyPI does not allow a version to be re-uploaded. Pulled that job into a reusable verify-wheel.yml with an optional artifact input, and wired it into release.yml between build-release and publish-pypi so the thing installed is the exact file about to be published. scheduled-ci.yml calls it with no input and keeps building from the checkout as before; the four shared steps are byte-identical to what it ran. Carried the tomllib/tomli fallback from main, and moved the tomli install into the step that reads pyproject rather than the build step, because the build step is skipped on the artifact path - leaving it there would have failed every release on Python 3.10 with the same ModuleNotFoundError that fix was for.
The first run of the temp file failed at startup, which is the answer I wanted and not the one I expected: verify-wheel.yml declared 'actions: read' for the artifact download, and a called workflow may not request more than its caller grants. scheduled-ci.yml caps its run at 'contents: read'. So this would not have broken the release it was written for. It would have broken the WEEKLY run, every Monday, at startup, in the one workflow whose entire purpose is noticing breakage nobody is watching for. verify-wheel.yml now declares no permissions and inherits from the caller. release.yml grants 'actions: read' on the calling job; scheduled-ci.yml grants nothing extra and needs nothing extra, because it builds its own wheel.
Both were scaffolding for proving this change works, and the proof is in the PR description rather than the tree.
Jules ReviewCOVERAGE: 77e51a3 3 files SummaryThis PR extracts the wheel verification logic into a reusable workflow ( VERDICT: approve This review never edits code or force-blocks a merge. No blocking issues were found, so this PR was auto-approved. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
release.ymlbuilt a wheel, rantwine checkon it, and uploaded it to PyPIwithout ever installing it.
The job that does install a wheel and import every module out of it already
existed, in
scheduled-ci.yml, and it is good. It just had one caller and thewrong one. The weekly run asks "did the world break rigout since our last
commit?" - a real question, and the reason it was written. Nobody was asking
"is the wheel we are about to publish installable?", and that is the question
where a wrong answer is permanent: PyPI does not allow a version to be
re-uploaded, so a broken 0.3.2 can only be answered by shipping 0.3.3 to
everyone who installed in between.
The bug class is invisible to everything else on the release path.
pip install -e .imports out ofsrc/, so it cannot see a module missing from the wheel, amis-declared package, dropped package data, or a console script pointing at a
function that does not exist.
twine checkreads metadata and never installsanything. Both are green on a wheel that fails for every user.
This is not hypothetical here. PR #21's own description records that "a clean
installed-wheel test also exposed a Windows venv redirector PID mismatch in
detached startup" - that test has already caught a real bug in this project, and
it was never on the path where it mattered most.
What changed
verify-wheel.yml(new, reusable) holds the job, with an optionalartifactinput. Empty means build from the checkout, the existing behaviour. Set means
download and verify that artifact instead.
release.ymlcalls it betweenbuild-releaseandpublish-pypi, passingrelease-dist, so what gets installed is the exact file that will bepublished rather than a rebuild that might differ.
scheduled-ci.ymlcalls it with no input. The four shared steps arebyte-identical to what it ran before.
Two things found while doing it
The version check could not have run on the release path.
mainrecentlyfixed the
tomllib/tomlifallback for Python 3.10, but installedtomliinthe build step. The build step is skipped when an artifact is passed, so
leaving it there would have failed every release-time run on 3.10 with the same
ModuleNotFoundErrorthat fix was for. The install moved to the step that readspyproject.toml.Declaring
actions: readin the reusable workflow breaks the weekly run. Acalled workflow may not request more permissions than its caller grants, and
scheduled-ci.ymlcaps its run atcontents: read. The first attempt failed atstartup for exactly this reason. That would not have broken the release it was
written for - it would have broken the Monday run, at startup, in the workflow
whose entire purpose is noticing breakage nobody is watching for. So
verify-wheel.ymlnow declares no permissions and inherits from its caller, andrelease.ymlgrantsactions: readon the calling job.Verification
Both paths were run on a temporary workflow on this branch, since the release
path is otherwise only reachable by pushing a tag, which publishes for real.
That workflow has been removed;
pyproject.tomlis identical tomain.Green, 13/13, with the conditional branching correctly in all twelve matrix
jobs -
build=skipped download=successon the artifact path,build=success download=skippedon the checkout path, acrossubuntu/macos/windows and Python 3.10/3.14.
Then the gate was checked in the other direction, because one that only ever
says yes is not a gate. A console script pointing at a function that does not
exist was added to
pyproject.toml.pip install -e .accepted it withoutcomplaint, which is the blind spot. All twelve jobs then failed with:
Unrelated, found while checking dates
CHANGELOG.mddates 0.3.1 as 2026-08-02. PyPI says it was uploaded2026-08-17. 0.3.0's date is correct. Left alone here rather than mixed into a CI
change.