ci(pypi): publish from a top-level job so trusted publishing can match#41
Merged
Conversation
PyPI matches a registered trusted publisher against the job_workflow_ref OIDC claim, which names the workflow file containing the publishing job; workflow_ref is never checked. With the publish running inside package-pypi.yml, that claim named the reusable workflow, and PyPI does not accept a reusable workflow as a publisher (warehouse#11096). This is what returned `invalid-publisher: valid token, but no corresponding publisher` on the 3.0.0 release, and no value entered in the publisher form could have matched it. Only the publish job moves. Building is untouched: build_sdist and the six-runner build_wheels matrix stay in package-pypi.yml and upload the same dist-sources and dist-wheels-<os> artifacts. Those are scoped to the workflow run, so the hoisted job downloads them across the call boundary with the same pattern and merge-multiple settings as before. The new job declares environment `pypi` and `id-token: write`, and states the release_created gate explicitly rather than relying on skip-propagation from the `pypi` job, so the condition survives an edit there. The package-name and environment-name inputs are removed along with the job that read them: an environment-name input that no longer selects the deployment environment would be a silent no-op on a security boundary. PYPI_API_TOKEN stays declared but inert in the reusable workflow; the rollback to classic token auth is the same secret, now consumed by release.yml. The npm and crates jobs are unchanged. npm's OIDC works from inside its reusable workflow — it reached Rekor and was rejected for duplicate content, a different failure from publisher matching. Registration this expects: owner dpezto, repository tree-sitter-gnuplot, workflow release.yml, environment pypi, as a pending publisher since the project does not yet exist on PyPI.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
Why
PyPI matches a registered trusted publisher against the
job_workflow_refOIDC claim — the workflow file containing the publishing job.workflow_refsits in warehouse's__unchecked_claims__and is never validated.With the publish running inside
package-pypi.yml, aworkflow_call-only reusable workflow, that claim named the reusable file rather thanrelease.yml. PyPI does not accept a reusable workflow as a publisher, per its own troubleshooting page and warehouse#11096. That is what returnedinvalid-publisher: valid token, but no corresponding publisheron the 3.0.0 release — and no value entered in the publisher form could have matched it, including therelease.ymlthe error'sworkflow_reffield invites.What moved
Only the publish job. Building is untouched.
build_sdistpackage-pypi.ymlbuild_wheels(6 runners)package-pypi.ymlrelease.ymlaspypi-publishThe build jobs still upload
dist-sourcesanddist-wheels-<os>. Artifacts are scoped to the workflow run, so the hoisted job downloads them across the call boundary with the samepattern: dist-*andmerge-multiple: trueas before.The hoisted job declares
environment: pypiandpermissions: id-token: write, and states therelease_createdgate explicitly rather than relying on skip-propagation from thepypijob, so the condition survives an edit there. Thepypireusable call correspondingly dropsid-token: write, which it no longer needs.Removed inputs
package-nameandenvironment-nameare removed along with the job that read them. Anenvironment-nameinput that no longer selects the deployment environment would be a silent no-op on a security boundary — worse than its absence. Both values now live explicitly inrelease.yml.PYPI_API_TOKENstays declared but inert in the reusable workflow. The rollback to classic token auth is the same secret, now consumed byrelease.yml'spypi-publishjob.npm and crates are unchanged
Neither job is touched by this diff. npm's OIDC works from inside its reusable workflow: it reached Rekor and was rejected for duplicate content, which is a different failure from publisher matching, and needs no structural change.
Registration this expects
A pending publisher, since the project does not yet exist on PyPI:
tree-sitter-gnuplotdpeztotree-sitter-gnuplotrelease.ymlpypiThe environment must be
pypiand not blank, matching the job's ownenvironment:declaration — a mismatch in either direction reproducesinvalid-publisher.