From d53c0650203c2987a790c753db6ec851326045b8 Mon Sep 17 00:00:00 2001 From: Jamie Nelson Date: Sat, 10 Oct 2026 13:11:33 +1100 Subject: [PATCH 1/2] fix(release): make GitHub release creation independently retryable --- .github/workflows/create_tag.yml | 34 +++++++++++++++----------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/.github/workflows/create_tag.yml b/.github/workflows/create_tag.yml index fd0a9e0..465c709 100644 --- a/.github/workflows/create_tag.yml +++ b/.github/workflows/create_tag.yml @@ -14,12 +14,16 @@ concurrency: jobs: tag: + name: Validate and publish release commit and tag if: >- github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main' && github.event.workflow_run.head_repository.full_name == github.repository runs-on: ubuntu-latest + outputs: + tag: ${{ steps.version.outputs.tag }} + commit: ${{ steps.version.outputs.commit }} env: TESTED_SHA: ${{ github.event.workflow_run.head_sha }} steps: @@ -46,14 +50,6 @@ jobs: with: node-version: '24.19.0' cache: 'yarn' - - name: Generate release changelog - id: tag_version - uses: mathieudutour/github-tag-action@v6.2 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - commit_sha: ${{ github.event.workflow_run.head_sha }} - tag_prefix: 'v' - dry_run: 'true' - name: Install project dependencies run: yarn install --frozen-lockfile - name: Validate and build the tested revision @@ -70,11 +66,6 @@ jobs: - name: Prepare and validate the release commit and tag id: version shell: bash - env: - CHANGELOG: | - chore(release): v%s - - ${{ steps.tag_version.outputs.changelog }} run: | set -euo pipefail git fetch --tags origin @@ -98,7 +89,7 @@ jobs: NODE ) # preversion runs unit tests; version updates the generated documentation. - yarn version --new-version "$next_version" --message "${CHANGELOG}" + yarn version --new-version "$next_version" --message 'chore(release): v%s' release_version=$(node -p 'require("./package.json").version') if [[ ! "$release_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo '::error::The release version is not a stable semantic version.' @@ -129,12 +120,19 @@ jobs: "${RELEASE_COMMIT}:refs/heads/main" \ "refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}" + # "Re-run failed jobs" can retry release creation after the tag and main + # commit were published, without revisiting the tag job's stale-main guard. + release: + name: Create GitHub release + needs: tag + runs-on: ubuntu-latest + steps: - name: Create a GitHub release uses: softprops/action-gh-release@v2.5.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ steps.version.outputs.tag }} - target_commitish: ${{ steps.version.outputs.commit }} - name: Release ${{ steps.version.outputs.tag }} - body: ${{ steps.tag_version.outputs.changelog }} + tag_name: ${{ needs.tag.outputs.tag }} + target_commitish: ${{ needs.tag.outputs.commit }} + name: Release ${{ needs.tag.outputs.tag }} + generate_release_notes: true From 35fba287cc7e23a0541665498896bdfdb797276a Mon Sep 17 00:00:00 2001 From: Jamie Nelson Date: Sat, 10 Oct 2026 13:30:37 +1100 Subject: [PATCH 2/2] fix: preserve releases across publication retries --- .github/workflows/create_tag.yml | 44 +++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/.github/workflows/create_tag.yml b/.github/workflows/create_tag.yml index 465c709..99473a3 100644 --- a/.github/workflows/create_tag.yml +++ b/.github/workflows/create_tag.yml @@ -127,12 +127,42 @@ jobs: needs: tag runs-on: ubuntu-latest steps: - - name: Create a GitHub release - uses: softprops/action-gh-release@v2.5.0 + - name: Create or resume the GitHub release + uses: actions/github-script@v8 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ needs.tag.outputs.tag }} + RELEASE_COMMIT: ${{ needs.tag.outputs.commit }} with: - tag_name: ${{ needs.tag.outputs.tag }} - target_commitish: ${{ needs.tag.outputs.commit }} - name: Release ${{ needs.tag.outputs.tag }} - generate_release_notes: true + script: | + const tag = process.env.RELEASE_TAG; + let release; + for await (const { data: releases } of github.paginate.iterator( + github.rest.repos.listReleases, + { ...context.repo, per_page: 100 }, + )) { + release = releases.find(candidate => candidate.tag_name === tag); + if (release) break; + } + if (release && !release.draft) { + core.info(`Release ${tag} is already published; nothing to do.`); + return; + } + if (release) { + await github.rest.repos.updateRelease({ + ...context.repo, + release_id: release.id, + draft: false, + make_latest: 'legacy', + }); + core.info(`Published existing draft ${tag}, preserving its notes.`); + } else { + await github.rest.repos.createRelease({ + ...context.repo, + tag_name: tag, + target_commitish: process.env.RELEASE_COMMIT, + name: `Release ${tag}`, + generate_release_notes: true, + make_latest: 'legacy', + }); + core.info(`Created release ${tag}.`); + }