feat(publish-py): add token auth mode as escape hatch from OIDC#10
Conversation
Adds optional `pypi-auth` input (`oidc` | `token`, default `oidc`) and
`PYPI_REGISTRY_TOKEN` secret to `publish-py.yml`. When `pypi-auth: token`,
the workflow publishes via twine with the supplied API token and skips
the GitHub Environment binding entirely.
Motivation: hop-top/poly-uri couldn't get PyPI OIDC trusted publishing
to match despite repeatedly correct claims (owner, repo, workflow
filename `publish.yml`, environment `pypi`). After multiple
delete-and-recreate cycles of the pending publisher, PyPI continued to
return `invalid-publisher: Publisher with matching claims was not
found`. The cause appears to be drift in PyPI's pending-publisher
matching that doesn't surface in the UI. Rather than block the py
release on debugging PyPI internals, callers can opt into token auth.
Wires through `publish-on-tag.yml`:
- new `PYPI_REGISTRY_TOKEN` secret (optional)
- new `pypi-auth` field in ecosystem entry (optional, default oidc)
SKILL.md updated with:
- PyPI auth modes section explaining oidc vs token tradeoffs
- OIDC trap callout: trusted publisher must match the CALLER
workflow filename, NOT the reusable's (workflow_ref claim comes
from the caller).
There was a problem hiding this comment.
Pull request overview
Adds an optional PyPI API-token authentication path to the shared Python publishing workflow (as an escape hatch when PyPI OIDC trusted publishing fails), wires that option through publish-on-tag, and documents the new modes in SKILL.md.
Changes:
- Split
publish-pyintopublish-oidc(default) andpublish-token(new) jobs selected by a newpypi-authinput. - Plumb
pypi-authand optionalPYPI_REGISTRY_TOKENthroughpublish-on-tag. - Document PyPI auth modes and common OIDC misconfiguration (“workflow_ref is caller filename”) in
SKILL.md.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
SKILL.md |
Documents PyPI auth modes (oidc vs token) and updates the ecosystems field reference. |
.github/workflows/publish-py.yml |
Adds pypi-auth input + optional token secret; introduces separate OIDC/token publish jobs. |
.github/workflows/publish-on-tag.yml |
Forwards pypi-auth and optional PYPI_REGISTRY_TOKEN into the Python publish reusable. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| publish-oidc: | ||
| if: ${{ inputs.pypi-auth == 'oidc' }} | ||
| runs-on: ubuntu-latest |
There was a problem hiding this comment.
Fixed in 3rd commit. New validate job runs a case statement on inputs.pypi-auth and fails the workflow with an explicit error before any publish job is dispatched. Both publish-oidc and publish-token now needs: validate.
| - name: Publish (API token) | ||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| packages-dir: ${{ inputs.working-directory }}/dist/ | ||
| password: ${{ secrets.PYPI_REGISTRY_TOKEN }} |
There was a problem hiding this comment.
Fixed in 3rd commit. publish-token now has a first step that reads secrets.PYPI_REGISTRY_TOKEN into a local env var and exits with ::error:: if empty, before checkout/setup/build. Wording in the message points at the caller's publish-on-tag secrets-passthrough as the most likely root cause.
Two failure modes Copilot review flagged: 1. Typoed `pypi-auth` value (e.g. `oicd`) skips both publish-oidc and publish-token, leaving the workflow green with nothing published. Add a `validate` job that fails fast on unknown values; both publish jobs depend on it. 2. `pypi-auth: token` with missing/empty `PYPI_REGISTRY_TOKEN` secret reaches the `pypa/gh-action-pypi-publish` step with empty password and dies with a confusing twine error. Add a preflight step that checks the secret and fails with a clear message before any other work.
PyPI OIDC trusted publishing failed 8x with `invalid-publisher` despite claims matching the pending publisher exactly. hop-top/.github#10 (in @v0 since v0.4.0) adds token-auth as an escape hatch — use it for py until the PyPI side is sorted. Wires PYPI_REGISTRY_TOKEN through from the org-level secret.
Adds optional pypi-auth=token mode (gated on PYPI_REGISTRY_TOKEN secret) to publish-py.yml, plumbed through publish-on-tag.yml. Default unchanged.
Motivation: hop-top/poly-uri's py publish failed 8x with PyPI invalid-publisher despite claims matching the pending publisher row exactly. After 3 delete+recreate cycles, error persisted. PyPI pending-publisher matching drift — needs PyPI support to fully resolve. Token mode unblocks py releases now and helps with bootstrap (PyPI's project-scoped trusted publishers can only be added AFTER first publish).
Changes:
Backwards compatible: existing callers untouched.
Test plan: