Skip to content

ci(release): publish to PyPI with trusted publishing - #77

Merged
VonAlphaBisZulu merged 1 commit into
mainfrom
switch-to-trusted-publishing
Jul 26, 2026
Merged

ci(release): publish to PyPI with trusted publishing#77
VonAlphaBisZulu merged 1 commit into
mainfrom
switch-to-trusted-publishing

Conversation

@VonAlphaBisZulu

Copy link
Copy Markdown
Contributor

This would have broken the next release

The stored PyPI API token is now deleted, but the publish step still passed:

with:
  password: ${{ secrets.PYPI_API_TOKEN }}

A missing secret resolves to an empty string rather than failing the expression. So the next release would have reached the upload with no credential — and no OIDC token either, because permissions: did not request id-token: write.

It would have failed late. With the steps reordered in #74, the version bump is pushed to main before the upload, so the result would be main carrying a version that never reached PyPI, with no tag or release for it.

The fix

Request id-token: write and drop the password input. pypa/gh-action-pypi-publish then falls back to trusted publishing, exchanging a short-lived OIDC token for an upload token against the publisher configured on PyPI.

Nothing durable is stored, so there is no token left to expire, leak, or rotate — which is the point of having deleted it.

Secret references after this change

Reference Where it lives
RELEASE_DEPLOY_KEY build_and_publish env ✅
NAME_GITHUB, MAIL_GITHUB build_and_publish env ✅
ANACONDA_API_TOKEN build_and_publish env ✅ (renewed, expires 2030-01-10)
PYPI_API_TOKEN no longer referenced

Repo-level secrets are now empty; everything the workflow needs is environment-scoped, and the job declares that environment.

Before the next release

The PyPI publisher must match exactly: owner klamt-lab, repository straindesign, workflow python-publish.yml, environment build_and_publish. If the environment field was left blank on PyPI it matches any environment, which also works — but a different value would not.

🤖 Generated with Claude Code

The stored PyPI API token has been deleted, but the publish step still passed
`password: ${{ secrets.PYPI_API_TOKEN }}`. A missing secret resolves to an empty
string rather than failing, so the next release would have reached the upload
with no credential at all, and no OIDC token either, because the workflow did not
request `id-token: write`.

It would have failed late: with the steps reordered, the version bump is pushed
to main before the upload runs, so main would carry a version that never reached
PyPI, and no tag or release would exist for it.

Request `id-token: write` and drop the password input. The action then falls back
to trusted publishing, exchanging a short-lived OIDC token for an upload token
against the publisher configured on PyPI for this repository, this workflow and
the build_and_publish environment. Nothing durable is stored, so there is no
token left to expire, leak or rotate.

Every remaining secret reference now resolves: RELEASE_DEPLOY_KEY, NAME_GITHUB,
MAIL_GITHUB and ANACONDA_API_TOKEN all exist in that environment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VonAlphaBisZulu
VonAlphaBisZulu merged commit cd6c41d into main Jul 26, 2026
20 checks passed
@VonAlphaBisZulu
VonAlphaBisZulu deleted the switch-to-trusted-publishing branch July 26, 2026 14:46
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