From 4076a4aa960ea6b81ef3d2b7d8e286c6dfa4a25e Mon Sep 17 00:00:00 2001 From: Cameron Brooks Date: Sat, 27 Jun 2026 20:32:23 -0400 Subject: [PATCH] ci(release): make the release workflow retry-safe A release can fail late (the 0.1.0 run created the tag + GitHub Release, then PyPI rejected the upload over a pending-publisher project-name mismatch). Recovering required deleting the tag by hand. Make every mutating step idempotent so a fixed re-run completes the publish: - validate: an existing tag is a notice, not a hard failure - tag creation is skipped when the tag already exists - PyPI publish uses skip-existing softprops/action-gh-release already updates an existing Release. --- .github/workflows/release.yml | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07d5836..03256b3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,12 +42,16 @@ jobs: fi echo "tag=v$VERSION" >> "$GITHUB_OUTPUT" - - name: Check tag is free + - name: Note tag status (retry-safe) run: | + # Don't fail on an existing tag: a release can fail late (e.g. PyPI) with + # the tag/Release already created. Re-running must complete the publish, + # so tag creation, the Release, and the upload are all made idempotent. TAG="${{ steps.check.outputs.tag }}" if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Error: tag $TAG already exists" - exit 1 + echo "::notice::tag $TAG already exists — treating this run as a retry" + else + echo "tag $TAG is new" fi - name: Verify version matches sources @@ -90,12 +94,17 @@ jobs: fi ls -lh dist/ - - name: Create and push tag + - name: Create and push tag (if new) run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag -a "${{ needs.validate.outputs.tag }}" -m "Release ${{ inputs.version }}" - git push origin "${{ needs.validate.outputs.tag }}" + TAG="${{ needs.validate.outputs.tag }}" + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "tag $TAG already exists — skipping creation (retry)" + else + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a "$TAG" -m "Release ${{ inputs.version }}" + git push origin "$TAG" + fi - name: Create GitHub Release uses: softprops/action-gh-release@v2 @@ -113,3 +122,4 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: print-hash: true + skip-existing: true # idempotent: a retry won't fail if files are already up