Skip to content

Commit ef815ea

Browse files
authored
fix(ci): stop nesting pypa/gh-action-pypi-publish in a composite action (#94)
* fix(ci): stop nesting pypa/gh-action-pypi-publish in a composite action Confirmed against four real release runs today: pypa/gh-action-pypi-publish is a Docker container action, and GitHub resolves its image using the wrapping action's own repository and ref rather than the Docker action's when nested inside another `uses:` -- every publish failed with `docker: invalid reference format`, trying to pull ghcr.io/reqstool/.github:<this-action's-own-sha>. The action's own maintainers say this usage is untested and unsupported. This is exactly what the workaround comment linked from #92's PR description already said -- upload as a bare step in the caller, not routed through anything else -- and actions/publish-to-pypi violated it by wrapping the publish step instead of just inlining it. Deleting the composite action rather than trimming it to only download-artifact: what remains is one actions/download-artifact call, not enough indirection to be worth a shared action, and every layer added here tonight has broken in a new way. Companion PRs inline both steps directly into each PyPI-publishing repo's release.yml. Signed-off-by: Jimisola Laursen <jimisola@jimisola.com> * refactor(ci): keep the composite action, drop only the publish step Reworks the previous commit, which deleted the action outright on the mistaken premise that composite actions were the problem. They are not: a composite action creates no new workflow context, so the job keeps the caller's OIDC identity -- which is exactly why it works where a reusable workflow does not. Someone makes the same point for Ruby in pypi/warehouse#11096. Only pypa/gh-action-pypi-publish has to come out. It is a Docker container action, and GitHub resolves a nested Docker action's image against the wrapping action's repository rather than its own -- the action's own maintainer describes the same breakage in that thread. Renamed publish-to-pypi -> download-dists, since what remains downloads the distributions and deliberately does not publish them. Keeping the old name would have been the misleading part. RELEASING.md's PyPI notes corrected while here. Two claims in the previous commit were wrong: PyPI matches `job_workflow_ref`, the bottom-most workflow, not the top-level caller; and reusable workflows are not rejected categorically -- one in the *same* repo as its caller works by accident of that same rule. What fails is a cross-repo one like this repo's, which puts reqstool/.github in the claim where the calling project has to be. Signed-off-by: Jimisola Laursen <jimisola@jimisola.com> --------- Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
1 parent b10b898 commit ef815ea

3 files changed

Lines changed: 68 additions & 62 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Download distributions
2+
description: >
3+
Download a built Python distribution artifact into dist/, ready for a publish
4+
step in the calling job.
5+
6+
# Deliberately does NOT publish, despite being the shared half of the PyPI
7+
# publish job. pypa/gh-action-pypi-publish is a Docker container action, and
8+
# GitHub resolves a nested Docker action's image against the *wrapping* action's
9+
# repository rather than its own: an earlier version of this action wrapped it,
10+
# and every publish failed with `docker: invalid reference format`, trying to
11+
# pull ghcr.io/reqstool/.github:<this action's own sha>. The action's own
12+
# maintainer describes the same breakage in pypi/warehouse#11096. The publish
13+
# step therefore stays inline in each caller's release.yml.
14+
#
15+
# A composite action remains the right shape for the rest. Unlike a
16+
# workflow_call reusable workflow, it does not create a new workflow context, so
17+
# the job keeps the caller's own OIDC identity -- which is what PyPI's trusted
18+
# publishing matches on, and why the publish job cannot live in a reusable
19+
# workflow. See RELEASING.md.
20+
21+
inputs:
22+
dry-run:
23+
description: "Validate the artifacts with twine check instead of leaving them for a publish step."
24+
required: false
25+
default: "false"
26+
artifact:
27+
description: "Name of the build artifact holding the distributions."
28+
required: false
29+
default: "dist"
30+
31+
runs:
32+
using: composite
33+
steps:
34+
- uses: actions/download-artifact@v8.0.1
35+
with:
36+
name: ${{ inputs.artifact }}
37+
path: dist
38+
39+
- name: Dry-run — validate artifacts
40+
if: ${{ inputs.dry-run == 'true' }}
41+
shell: bash
42+
run: |
43+
# renovate: datasource=pypi depName=twine
44+
pip install --quiet twine==7.0.0
45+
twine check --strict dist/*

.github/actions/publish-to-pypi/action.yml

Lines changed: 0 additions & 52 deletions
This file was deleted.

RELEASING.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,27 @@ cleaner option, as below.
123123
> pypi.org (project → Publishing) must read `stable`. The same applies to any other registry
124124
> that binds an OIDC identity to an environment name.
125125
126-
> **PyPI does not accept a reusable workflow as the trusted publisher at all**, not even with
127-
> the right names —
128-
> [pypi/warehouse#11096](https://github.com/pypi/warehouse/issues/11096), unresolved as of
129-
> this writing, confirmed against this org's own release runs on 2026-08-23. The job that
130-
> calls `pypa/gh-action-pypi-publish` must be defined directly in the caller's own workflow
131-
> file; `actions/publish-to-pypi` is a composite action, not a `workflow_call` workflow, for
132-
> exactly this reason — a job that uses it for steps still belongs to the caller's own
133-
> workflow for OIDC purposes. Every PyPI-publishing repo's `release.yml` must call it this
134-
> way, never through another reusable workflow.
126+
> **The PyPI publish job cannot live in a reusable workflow in this repo.** PyPI matches the
127+
> OIDC `job_workflow_ref` claim — the *bottom-most* workflow that ran the job — against the
128+
> Trusted Publisher's owner, repo and filename. A `workflow_call` into
129+
> `reqstool/.github` puts *this* repository in that claim, which can never match a Trusted
130+
> Publisher configured for the calling project, so it fails with `invalid-publisher` no matter
131+
> what the config names. (A reusable workflow in the *same* repo as its caller does work, by
132+
> accident of the same matching rule — that is not what this org does.)
133+
> [pypi/warehouse#11096](https://github.com/pypi/warehouse/issues/11096) tracks proper support;
134+
> unresolved as of this writing.
135+
>
136+
> **A composite action is fine, and is what this repo uses.** Unlike a reusable workflow it
137+
> creates no new workflow context, so the job keeps the caller's own identity — hence
138+
> `actions/download-dists`.
139+
>
140+
> **But `pypa/gh-action-pypi-publish` may not be nested inside one.** It is a Docker container
141+
> action, and GitHub resolves a nested Docker action's image against the wrapping action's
142+
> repository rather than its own — an earlier version of `download-dists` wrapped it and every
143+
> publish failed with `docker: invalid reference format`. Its own maintainer
144+
> [describes the same breakage](https://github.com/pypi/warehouse/issues/11096#issuecomment-2871981512)
145+
> ("GH doesn't work well with nested composite actions ... making it defunct"). It stays a bare
146+
> step, inline, in every PyPI-publishing repo's `release.yml`.
135147
136148
## Cutting a release candidate
137149

@@ -200,7 +212,8 @@ prepare (dry-run stops here)
200212
→ [approval] tag common-release-tag.yml
201213
→ build @ tag the repo's own build.yml, ref = the tag
202214
→ assets common-release-assets.yml
203-
→ publish actions/publish-to-pypi (job in the caller's own workflow)
215+
→ publish actions/download-dists + an inline publish step,
216+
in the caller's own workflow (PyPI)
204217
/ java-publish-to-maven.yml / …
205218
→ promote common-release-promote.yml
206219
```

0 commit comments

Comments
 (0)