Skip to content

ci: verify the wheel installs before publishing it - #38

Merged
github-actions[bot] merged 4 commits into
mainfrom
ci/verify-wheel-before-publish
Aug 18, 2026
Merged

ci: verify the wheel installs before publishing it#38
github-actions[bot] merged 4 commits into
mainfrom
ci/verify-wheel-before-publish

Conversation

@melbinjp

Copy link
Copy Markdown
Owner

release.yml built a wheel, ran twine check on it, and uploaded it to PyPI
without 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 the
wrong 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 of src/, so it cannot see a module missing from the wheel, a
mis-declared package, dropped package data, or a console script pointing at a
function that does not exist. twine check reads metadata and never installs
anything. 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 optional artifact
    input. Empty means build from the checkout, the existing behaviour. Set means
    download and verify that artifact instead.
  • release.yml calls it between build-release and publish-pypi, passing
    release-dist, so what gets installed is the exact file that will be
    published rather than a rebuild that might differ.
  • scheduled-ci.yml calls it with no input. The four shared steps are
    byte-identical to what it ran before.

Two things found while doing it

The version check could not have run on the release path. main recently
fixed the tomllib/tomli fallback for Python 3.10, but installed tomli in
the 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
ModuleNotFoundError that fix was for. The install moved to the step that reads
pyproject.toml.

Declaring actions: read in the reusable workflow breaks the weekly run. A
called workflow may not request more permissions than its caller grants, and
scheduled-ci.yml caps its run at contents: read. The first attempt failed at
startup 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.yml now declares no permissions and inherits from its caller, and
release.yml grants actions: read on 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.toml is identical to main.

Green, 13/13, with the conditional branching correctly in all twelve matrix
jobs - build=skipped download=success on the artifact path,
build=success download=skipped on the checkout path, across
ubuntu/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 without
complaint, which is the blind spot. All twelve jobs then failed with:

BROKEN entry point: rigout-broken = rigout.server:function_that_does_not_exist
-> AttributeError: module 'rigout.server' has no attribute 'function_that_does_not_exist'

Unrelated, found while checking dates

CHANGELOG.md dates 0.3.1 as 2026-08-02. PyPI says it was uploaded
2026-08-17. 0.3.0's date is correct. Left alone here rather than mixed into a CI
change.

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.
@github-actions
github-actions Bot enabled auto-merge (squash) August 18, 2026 07:53
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

Jules Review

COVERAGE: 77e51a3 3 files

Summary

This PR extracts the wheel verification logic into a reusable workflow (verify-wheel.yml) and inserts it into the release path (release.yml) right before publication to PyPI, closing a critical gap where a broken wheel could be published. It correctly tests the exact artifact that will be uploaded. The changes handle the tomli fallback properly across both artifact and checkout modes and thoughtfully manage GitHub Actions permissions to prevent breaking the scheduled CI run.

VERDICT: approve


This review never edits code or force-blocks a merge. No blocking issues were found, so this PR was auto-approved.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: Jules found no blocking issues (verdict: approve). See the review comment above.

@github-actions
github-actions Bot merged commit f33f7cd into main Aug 18, 2026
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant