Summary
Two independent GitHub Actions limitations mean PyPI publishing via trusted publishing
(OIDC) cannot be factored into a shared reusable component in this org — not a workflow,
and not a composite action either. Every PyPI-publishing repo has to inline both steps of
the publish job directly in its own release.yml. This issue documents why, for whoever
next tries to DRY this up.
Limitation 1: no reusable workflow
Upstream: pypi/warehouse#11096 —
"Trusted publishing: Support for GitHub reusable workflows", open, unresolved.
PyPI's trusted publisher matches OIDC claims against the workflow file the job actually
runs in. A workflow_call reusable workflow doesn't carry that identity the way PyPI
expects, so a job that dispatches pypa/gh-action-pypi-publish from inside one always fails
with invalid-publisher: valid token, but no corresponding publisher, regardless of what
the trusted publisher config on pypi.org names.
Confirmed against this org's own python-publish-to-pypi.yml on 2026-08-23: four real
release runs failed identically, and it explained why publish-dev-to-testpypi.yml had been
silently failing on every push to main since the 2026-08-16 migration.
The documented workaround, from a comment on the upstream
issue pointing at
OpenAstronomy's
docs:
build can stay in a reusable workflow, but the job calling pypa/gh-action-pypi-publish must
be defined directly in the caller's own workflow file.
Limitation 2: no composite action either
Not filed upstream as far as I've found — surfaced here first, from real release runs, the
same day.
The first fix (#92) moved the publish job out
of a reusable workflow and into a composite action, actions/publish-to-pypi, called
directly from each caller's release.yml. That satisfies limitation 1 — the job belongs to
the caller's workflow file for OIDC purposes — but pypa/gh-action-pypi-publish is a
Docker container action, and nesting one inside another uses: (a composite action, in
this case) breaks independently of the OIDC question.
Every real publish through actions/publish-to-pypi failed:
docker run ... ghcr.io/reqstool/.github:b10b898cd5a1d552a578dbe4f170f84fb8f98b6c
docker: invalid reference format
GitHub resolved the Docker image using the wrapping composite action's own repository and
pinned ref, not pypa/gh-action-pypi-publish's actual image. The action's own maintainers
say this usage is untested and
unsupported —
which the OpenAstronomy example already implied, showing the publish step as a bare,
unwrapped step, not something we read closely enough the first time.
Fixed in #94 by deleting
actions/publish-to-pypi rather than trimming it: what would remain — one
actions/download-artifact call — isn't enough to justify a shared action on its own.
The workaround, in full
Every PyPI-publishing repo's release.yml now inlines both steps directly, with no shared
component for either:
publish-to-pypi:
needs: [prepare, assets]
runs-on: ubuntu-latest
environment:
name: stable
url: https://pypi.org
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v8.0.1
with:
name: dist-tagged
path: dist
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
attestations: true
This is the one publish mechanism in the org's release flow with no shared action or
workflow behind it — npm, Maven Central, the Gradle Plugin Portal and the VS Code
Marketplace all factor cleanly. Worth revisiting if pypi/warehouse#11096 is ever resolved,
or if pypa/gh-action-pypi-publish documents composite-action support as supported rather
than untested.
Summary
Two independent GitHub Actions limitations mean PyPI publishing via trusted publishing
(OIDC) cannot be factored into a shared reusable component in this org — not a workflow,
and not a composite action either. Every PyPI-publishing repo has to inline both steps of
the publish job directly in its own
release.yml. This issue documents why, for whoevernext tries to DRY this up.
Limitation 1: no reusable workflow
Upstream: pypi/warehouse#11096 —
"Trusted publishing: Support for GitHub reusable workflows", open, unresolved.
PyPI's trusted publisher matches OIDC claims against the workflow file the job actually
runs in. A
workflow_callreusable workflow doesn't carry that identity the way PyPIexpects, so a job that dispatches
pypa/gh-action-pypi-publishfrom inside one always failswith
invalid-publisher: valid token, but no corresponding publisher, regardless of whatthe trusted publisher config on pypi.org names.
Confirmed against this org's own
python-publish-to-pypi.ymlon 2026-08-23: four realrelease runs failed identically, and it explained why
publish-dev-to-testpypi.ymlhad beensilently failing on every push to
mainsince the 2026-08-16 migration.The documented workaround, from a comment on the upstream
issue pointing at
OpenAstronomy's
docs:
build can stay in a reusable workflow, but the job calling
pypa/gh-action-pypi-publishmustbe defined directly in the caller's own workflow file.
Limitation 2: no composite action either
Not filed upstream as far as I've found — surfaced here first, from real release runs, the
same day.
The first fix (#92) moved the publish job out
of a reusable workflow and into a composite action,
actions/publish-to-pypi, calleddirectly from each caller's
release.yml. That satisfies limitation 1 — the job belongs tothe caller's workflow file for OIDC purposes — but
pypa/gh-action-pypi-publishis aDocker container action, and nesting one inside another
uses:(a composite action, inthis case) breaks independently of the OIDC question.
Every real publish through
actions/publish-to-pypifailed:GitHub resolved the Docker image using the wrapping composite action's own repository and
pinned ref, not
pypa/gh-action-pypi-publish's actual image. The action's own maintainerssay this usage is untested and
unsupported —
which the OpenAstronomy example already implied, showing the publish step as a bare,
unwrapped step, not something we read closely enough the first time.
Fixed in #94 by deleting
actions/publish-to-pypirather than trimming it: what would remain — oneactions/download-artifactcall — isn't enough to justify a shared action on its own.The workaround, in full
Every PyPI-publishing repo's
release.ymlnow inlines both steps directly, with no sharedcomponent for either:
This is the one publish mechanism in the org's release flow with no shared action or
workflow behind it — npm, Maven Central, the Gradle Plugin Portal and the VS Code
Marketplace all factor cleanly. Worth revisiting if pypi/warehouse#11096 is ever resolved,
or if
pypa/gh-action-pypi-publishdocuments composite-action support as supported ratherthan untested.