Skip to content

ci: publish PyPI from approved GitHub Releases with Trusted Publishing #287

Description

@lfnovo

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:

  1. Trigger the publish workflow on release.published.
  2. Require the release tag to be v<pyproject version> and resolve to the intended commit.
  3. Publish through PyPI Trusted Publishing using OIDC (id-token: write) instead of a stored API token.
  4. Run the upload job in a protected pypi GitHub environment with required approval and tag restrictions.
  5. Remove or make workflow_dispatch non-publishing so it cannot bypass the release boundary.
  6. Build once, pass the exact artifacts between jobs, and use pypa/gh-action-pypi-publish@release/v1 so PyPI attestations are generated.
  7. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-designIssue needs design/spec work before it can be picked up for implementation

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions