From d181eeed60e2f33dd5eb0e3d5e86ae16e9d75516 Mon Sep 17 00:00:00 2001 From: DeviousCardi <115358213+DeviousCardi@users.noreply.github.com> Date: Wed, 9 Sep 2026 22:29:19 +0530 Subject: [PATCH] release: create the release before uploading into it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `v0.9.0` failed. Both matrix legs built their binaries, archived them and computed their checksums, then retried `release not found` eleven times and exited 1 — so the tag existed, the artefacts existed, and nothing was published. The workflow's own comment carried the wrong assumption: that `upload-rust-binary-action` uploads "to the release the pushed tag names, creating it if this is the first artefact for it". It does not create one. It waits for a release to exist and fails when none appears, and pushing a tag creates a tag, not a release. With two matrix legs both waiting, there was nobody to create it. A `release` job now creates it once, before any build, and `build` depends on it. Deliberately one job rather than a step inside the matrix, so the two legs cannot race to create the same release. Two details the next re-run depends on: - it is idempotent — `gh release view` first, and exit 0 if the release is already there, so re-running a partly failed release does not fail here; - notes come from the tag's own annotation via `fetch-depth: 0`, with a fallback line for an unannotated tag, because `gh release create` rejects an empty `--notes-file`. v0.9.0 itself was recovered by hand — the release was created and the failed jobs re-run, and both architectures are attached. This is so the next tag does not need that. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/release.yml | 41 +++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b61ff94..62d46ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,41 @@ permissions: contents: write jobs: + # Pushing a tag creates a tag, not a release, and the upload action below + # needs a release to attach to. One job creates it, once, before any build + # starts — rather than letting the two matrix legs race to create the same + # one. + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The tag's own annotation becomes the release notes, so `fetch-depth` + # has to be deep enough to carry the tag object rather than just the + # commit it points at. + fetch-depth: 0 + - name: Create the release this tag names, if it is not there already + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + # Idempotent: a re-run of a partly failed release must not fail here. + if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then + echo "release $GITHUB_REF_NAME already exists, leaving it alone" + exit 0 + fi + git tag -l --format='%(contents)' "$GITHUB_REF_NAME" > notes.md + # An unannotated tag leaves notes.md empty, which `gh` rejects. + if [ ! -s notes.md ]; then + echo "Release $GITHUB_REF_NAME." > notes.md + fi + gh release create "$GITHUB_REF_NAME" \ + --verify-tag \ + --title "$GITHUB_REF_NAME" \ + --notes-file notes.md + build: + needs: release strategy: fail-fast: false matrix: @@ -29,8 +63,11 @@ jobs: with: key: ${{ matrix.target }} # Builds (cross-compiling for aarch64 via `cross`, which this action - # drives itself), archives, and uploads to the release the pushed tag - # names, creating it if this is the first artefact for it. + # drives itself), archives, and uploads to the release the `release` job + # above created. This action does *not* create the release itself — it + # waits for one to exist and then fails. v0.9.0 was tagged believing it + # did: both matrix legs built their binaries, then retried `release not + # found` eleven times and exited 1. - uses: taiki-e/upload-rust-binary-action@f0d45ae91ee7b8ee928de7a9d04d893a08bcbec6 # v1.30.2 with: bin: specmatrix