Skip to content

Commit f7de697

Browse files
committed
fix(ci): make a failed publish retryable, and unpin the stale twine
v0.11.0 was tagged and its GitHub Release created, but the PyPI upload failed: Checking dist/hotdata_framework-0.11.0-py3-none-any.whl: ERROR InvalidDistribution: Invalid distribution metadata: '2.5' is not a valid metadata version Nothing to do with the code. gh-action-pypi-publish was pinned at v1.13.0 (Sept 2025), whose bundled twine predates `Metadata-Version: 2.5`, which current hatchling emits. Bumped to v1.14.2. Note the build job's own `twine check --strict` PASSED, because it pip-installs a current twine. So the incompatibility was invisible until upload, by which point the tag was already public -- the check that exists to catch bad metadata cannot catch this class at all. RETRYABILITY IS THE REAL FIX. Both workflows triggered only on tag push, so a publish that failed for reasons unrelated to the code left two bad options: delete and re-push the tag, or burn a version number on a CI fix. Neither is a reasonable answer to "the upload failed, run it again". Both now accept a workflow_dispatch with a tag input, checkout that ref, and derive the version from it. release.yml gets the same treatment for a second reason: RELEASING.md already documents the recovery command gh workflow run "GitHub Release" --ref main -f tag=vX.Y.Z and the trigger it needs was never there, so that documented path has always failed. This makes the doc true. Verified both files parse with triggers ['push', 'workflow_dispatch'] and a `tag` input, and no GITHUB_REF_NAME references remain in either.
1 parent 54b33f0 commit f7de697

2 files changed

Lines changed: 44 additions & 8 deletions

File tree

‎.github/workflows/publish.yml‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,20 @@ on:
44
push:
55
tags:
66
- 'v[0-9]*'
7+
# Retry an existing tag without moving it. A publish can fail for reasons that
8+
# have nothing to do with the code — a stale action pin, a PyPI outage — and
9+
# with only the tag trigger the choices were to delete and re-push the tag or
10+
# to burn a version number on a CI fix. Neither is a good answer to
11+
# "the upload failed, run it again".
12+
workflow_dispatch:
13+
inputs:
14+
tag:
15+
description: Existing tag to build and publish (e.g. v1.2.3)
16+
required: true
17+
type: string
718

819
concurrency:
9-
group: pypi-publish-${{ github.ref_name }}
20+
group: pypi-publish-${{ inputs.tag || github.ref_name }}
1021
cancel-in-progress: false
1122

1223
permissions:
@@ -16,8 +27,13 @@ jobs:
1627
build:
1728
name: Build distribution
1829
runs-on: ubuntu-latest
30+
env:
31+
# The tag being released, whether it arrived by push or by dispatch.
32+
TAG: ${{ inputs.tag || github.ref_name }}
1933
steps:
2034
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
35+
with:
36+
ref: ${{ inputs.tag || github.ref_name }}
2137

2238
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
2339
with:
@@ -28,11 +44,11 @@ jobs:
2844

2945
- name: Verify tag matches pyproject version
3046
run: |
31-
if [[ ! "$GITHUB_REF_NAME" =~ ^v[0-9] ]]; then
32-
echo "Release tag '$GITHUB_REF_NAME' must start with 'v' followed by a digit (e.g. v1.0.0)" >&2
47+
if [[ ! "$TAG" =~ ^v[0-9] ]]; then
48+
echo "Release tag '$TAG' must start with 'v' followed by a digit (e.g. v1.0.0)" >&2
3349
exit 1
3450
fi
35-
tag="${GITHUB_REF_NAME#v}"
51+
tag="${TAG#v}"
3652
pkg_version=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
3753
if [ "$tag" != "$pkg_version" ]; then
3854
echo "Release tag ($tag) does not match pyproject.toml version ($pkg_version)" >&2
@@ -65,5 +81,10 @@ jobs:
6581
name: dist
6682
path: dist/
6783

84+
# v1.13.0's bundled twine rejects `Metadata-Version: 2.5`, which current
85+
# hatchling emits: `InvalidDistribution: '2.5' is not a valid metadata
86+
# version`. The build job's own `twine check --strict` passes, because it
87+
# pip-installs a current twine — so the failure appears only at upload,
88+
# after the tag is already public.
6889
- name: Publish via Trusted Publishing
69-
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
90+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2

‎.github/workflows/release.yml‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ on:
44
push:
55
tags:
66
- 'v[0-9]*'
7+
# RELEASING.md already documents this recovery path — "If PyPI publish
8+
# succeeded but the GitHub Release workflow failed, rerun it from main without
9+
# retagging" — but the trigger it needs was never here, so the documented
10+
# command has always failed. Adding it makes the doc true.
11+
workflow_dispatch:
12+
inputs:
13+
tag:
14+
description: Existing tag to create or update a Release for (e.g. v1.2.3)
15+
required: true
16+
type: string
717

818
permissions:
919
contents: write
@@ -12,8 +22,13 @@ jobs:
1222
release:
1323
name: Create GitHub Release
1424
runs-on: ubuntu-latest
25+
env:
26+
# The tag being released, whether it arrived by push or by dispatch.
27+
TAG: ${{ inputs.tag || github.ref_name }}
1528
steps:
1629
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
30+
with:
31+
ref: ${{ inputs.tag || github.ref_name }}
1732

1833
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
1934
with:
@@ -23,15 +38,15 @@ jobs:
2338
id: meta
2439
run: |
2540
pkg_name=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['name'])")
26-
pkg_version="${GITHUB_REF_NAME#v}"
41+
pkg_version="${TAG#v}"
2742
echo "name=${pkg_name}" >> "$GITHUB_OUTPUT"
2843
echo "version=${pkg_version}" >> "$GITHUB_OUTPUT"
2944
3045
- name: Extract changelog notes
3146
id: notes
3247
run: |
3348
set -euo pipefail
34-
version="${GITHUB_REF_NAME#v}"
49+
version="${TAG#v}"
3550
if [[ -f CHANGELOG.md ]]; then
3651
body="$(python scripts/extract-changelog.py "$version")"
3752
else
@@ -47,7 +62,7 @@ jobs:
4762
- name: Create GitHub Release
4863
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2
4964
with:
50-
tag_name: ${{ github.ref_name }}
65+
tag_name: ${{ inputs.tag || github.ref_name }}
5166
name: ${{ steps.meta.outputs.name }} ${{ steps.meta.outputs.version }}
5267
body: ${{ steps.notes.outputs.body }}
5368
generate_release_notes: false

0 commit comments

Comments
 (0)