Add PyPI Trusted Publishing release workflow - #9
Merged
Merged
Conversation
Publishes via OIDC instead of an API token. GitHub mints a short-lived identity token that PyPI exchanges for an upload token scoped to a single publish, so no long-lived credential is stored in the repository, in GitHub secrets, or on a maintainer's machine. A leaked token cannot be replayed because there is no token to leak. Build and publish are separate jobs, as the PyPA action documents: the build job runs with no elevated permissions, and only the publishing job is granted id-token: write. Distributions move between them as an artifact. Tag pushes matching v* publish to PyPI. A manual workflow_dispatch run can target TestPyPI instead, for a dry run before a real release. The build job fails when a tag does not match the version in pyproject.toml. That mismatch is what forced both 0.7.2 and 0.7.3 to be cut as separate versions rather than re-tagged, and it is worth catching mechanically. Signed PEP 740 attestations are produced by default under Trusted Publishing and are tied to the same OIDC identity that authorises the upload. This workflow is repository infrastructure and is not part of any distribution: .github/ is absent from the sdist and wheel, so no version bump is needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Replaces token-based publishing with OIDC Trusted Publishing. No API token is
stored in the repository, in GitHub secrets, or on a maintainer's machine — a
leaked token can't be replayed because there is no token to leak.
Structure
Build and publish are separate jobs, per the PyPA action's guidance. The build
job runs with no elevated permissions; only the publishing job gets
id-token: write. Distributions pass between them as an artifact.v*testpypipypiSigned PEP 740 attestations are generated by default under Trusted Publishing,
tied to the same OIDC identity that authorises the upload.
Tag/version guard
The build fails when a pushed tag doesn't match
versioninpyproject.toml:That exact mismatch is why 0.7.2 and 0.7.3 had to be cut as separate versions
instead of re-tagged. Worth catching mechanically rather than by memory.
Two steps needed before this can publish
Both are on your account and I can't do them.
Register the trusted publisher on PyPI — pypi.org → Your projects →
tappay→ Manage → Publishing → GitHub tab:shihweilotappay-pythonrelease.ymlpypiRepeat on test.pypi.org with environment
testpypiif you want the dry run.Requires owner access to the existing
tappayproject.Create the
pypiGitHub environment (Settings → Environments). Optionalfor function but strongly recommended: it lets you require manual approval
before any publish actually runs. If you register an environment name on
PyPI, the workflow must use the same one — it already does.
Publishing 0.7.3 once this lands
v0.7.3points ate46d7f5, which predates this workflow, so re-pushing thattag would not find it. Use a manual run with target
pypiinstead: it buildsmaster, which is the same commit plus this workflow, and.github/is notpart of the sdist or wheel — so the artifacts are equivalent to those built at
the tag. Releases from 0.8.0 onward will publish automatically on tag push.
🤖 Generated with Claude Code