Context
The v2.27.0 release showed that pushing a version tag immediately triggers an irreversible PyPI upload. This leaves no distinct GitHub-native boundary between creating the release reference and approving production publication.
The current workflow also publishes with a long-lived PYPI_TOKEN and permits workflow_dispatch. Current PyPA guidance recommends Trusted Publishing, a dedicated GitHub environment, and manual approval for production PyPI publishing.
Proposed direction
Use a published GitHub Release as the human-visible publication boundary while keeping the tag as the immutable source identity:
- Trigger the publish workflow on
release.published.
- Require the release tag to be
v<pyproject version> and resolve to the intended commit.
- Publish through PyPI Trusted Publishing using OIDC (
id-token: write) instead of a stored API token.
- Run the upload job in a protected
pypi GitHub environment with required approval and tag restrictions.
- Remove or make
workflow_dispatch non-publishing so it cannot bypass the release boundary.
- Build once, pass the exact artifacts between jobs, and use
pypa/gh-action-pypi-publish@release/v1 so PyPI attestations are generated.
- Update the maintainer profile, runbook, and Makefile commands to reflect the new trigger and recovery procedure.
Design decisions
- Whether the GitHub Release must reference a pre-existing tag or creates the tag itself.
- Who can approve the
pypi environment and whether self-review is allowed for a single-maintainer project.
- Whether TestPyPI should become a required pre-production gate.
- How release notes are created and approved before
release.published.
Exit criteria
- Publishing cannot start from a tag push alone.
- No long-lived PyPI token remains in the workflow or repository secrets.
- The workflow rejects tag/version/commit mismatches before upload.
- Production upload requires the configured environment protections.
- A dry-run or TestPyPI path validates artifacts without risking the production version.
- Release documentation and maintainer automation agree on the new boundary.
References
Context
The v2.27.0 release showed that pushing a version tag immediately triggers an irreversible PyPI upload. This leaves no distinct GitHub-native boundary between creating the release reference and approving production publication.
The current workflow also publishes with a long-lived
PYPI_TOKENand permitsworkflow_dispatch. Current PyPA guidance recommends Trusted Publishing, a dedicated GitHub environment, and manual approval for production PyPI publishing.Proposed direction
Use a published GitHub Release as the human-visible publication boundary while keeping the tag as the immutable source identity:
release.published.v<pyproject version>and resolve to the intended commit.id-token: write) instead of a stored API token.pypiGitHub environment with required approval and tag restrictions.workflow_dispatchnon-publishing so it cannot bypass the release boundary.pypa/gh-action-pypi-publish@release/v1so PyPI attestations are generated.Design decisions
pypienvironment and whether self-review is allowed for a single-maintainer project.release.published.Exit criteria
References