Skip to content

Add PyPI Trusted Publishing release workflow - #9

Merged
shihweilo merged 1 commit into
masterfrom
ci/trusted-publishing
Sep 9, 2026
Merged

shihweilo merged 1 commit into
masterfrom
ci/trusted-publishing

Conversation

@shihweilo

Copy link
Copy Markdown
Owner

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.

Trigger Result
Push a tag matching v* Build, then publish to PyPI
Manual run, target testpypi Build, then publish to TestPyPI (dry run)
Manual run, target pypi Build, then publish to PyPI

Signed 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 version in pyproject.toml:

tag=0.7.3 packaged=0.7.3     -> pass
tag=0.9.9 packaged=0.7.3     -> error, build stops

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.

  1. Register the trusted publisher on PyPI — pypi.org → Your projects →
    tappay → Manage → Publishing → GitHub tab:

    Field Value
    Owner shihweilo
    Repository tappay-python
    Workflow name release.yml
    Environment pypi

    Repeat on test.pypi.org with environment testpypi if you want the dry run.
    Requires owner access to the existing tappay project.

  2. Create the pypi GitHub environment (Settings → Environments). Optional
    for 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.3 points at e46d7f5, which predates this workflow, so re-pushing that
tag would not find it. Use a manual run with target pypi instead: it builds
master, which is the same commit plus this workflow, and .github/ is not
part 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

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>
@shihweilo
shihweilo merged commit 8029425 into master Sep 9, 2026
7 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