From f4f54ea43e554bb376fce0c0e71241affa88d72e Mon Sep 17 00:00:00 2001 From: Martijn Swaagman Date: Tue, 11 Aug 2026 22:51:09 +0200 Subject: [PATCH 1/3] fix(release): draft releases until assets are attached MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit release-please published each GitHub Release as non-draft immediately on merge, so releases/latest/download/... began redirecting to the new tag before the build+attach jobs uploaded any assets. During that multi-minute window every asset URL 404'd — including install.sh itself, which the install one-liner fetches — so a fresh install could fail. Create the release as a draft (release-please 'draft: true') and publish it (gh release edit --draft=false --latest) only after all assets are attached. A draft is never served as 'latest', so the pointer stays on the previous, fully-populated release until the new one is complete. If build/attach fail, the release stays a draft and is never exposed to users. --- .github/workflows/release.yml | 15 +++++++++++++++ release-please-config.json | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c42fe1a7..027047de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -143,8 +143,18 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | + # release-please creates the release as a draft (see + # release-please-config.json). A draft is never served as "latest", + # so `releases/latest/download/...` keeps resolving to the previous, + # fully-populated release until we publish below — closing the window + # where the one-liner would 404 on assets (including install.sh + # itself) that haven't finished uploading yet. TAG="${{ needs.release-please.outputs.tag_name }}" if [ -z "$TAG" ]; then + # Fallback for the workflow_dispatch publish_only retry path, which + # skips release-please and therefore has no tag_name output. Drafts + # are included in `gh release list` by default, so this still finds + # the pending draft release. TAG="$(gh release list --limit 1 --json tagName --jq '.[0].tagName')" fi gh release upload "$TAG" \ @@ -154,3 +164,8 @@ jobs: install.sh \ install.ps1 \ --clobber + # Publish only now that every asset is attached. Marking it latest + # flips the `releases/latest` pointer atomically to a release that is + # already complete. If build/attach fail, the release stays a draft + # and is never exposed to users — re-run via the publish_only input. + gh release edit "$TAG" --draft=false --latest diff --git a/release-please-config.json b/release-please-config.json index ba79cefd..8fa52e5d 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -19,7 +19,8 @@ "rust": { "release-type": "rust", "component": "gddy", - "include-component-in-tag": false + "include-component-in-tag": false, + "draft": true } } } From 083f63c0773fee9ba21119c9b225ea4b93a427dd Mon Sep 17 00:00:00 2001 From: Martijn Swaagman Date: Tue, 11 Aug 2026 22:53:53 +0200 Subject: [PATCH 2/3] docs(release): trim workflow comments to terse why notes --- .github/workflows/release.yml | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 027047de..21d308db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -143,18 +143,11 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | - # release-please creates the release as a draft (see - # release-please-config.json). A draft is never served as "latest", - # so `releases/latest/download/...` keeps resolving to the previous, - # fully-populated release until we publish below — closing the window - # where the one-liner would 404 on assets (including install.sh - # itself) that haven't finished uploading yet. + # Release is a draft (release-please-config.json) so `latest` never + # points at it until every asset is attached — avoids a 404 window. TAG="${{ needs.release-please.outputs.tag_name }}" if [ -z "$TAG" ]; then - # Fallback for the workflow_dispatch publish_only retry path, which - # skips release-please and therefore has no tag_name output. Drafts - # are included in `gh release list` by default, so this still finds - # the pending draft release. + # publish_only retry path has no tag_name; drafts are listed here. TAG="$(gh release list --limit 1 --json tagName --jq '.[0].tagName')" fi gh release upload "$TAG" \ @@ -164,8 +157,5 @@ jobs: install.sh \ install.ps1 \ --clobber - # Publish only now that every asset is attached. Marking it latest - # flips the `releases/latest` pointer atomically to a release that is - # already complete. If build/attach fail, the release stays a draft - # and is never exposed to users — re-run via the publish_only input. + # Publish only now assets are complete; stays a draft if build fails. gh release edit "$TAG" --draft=false --latest From 1c0fe1ff27830d0d1b4f31cdf9aa74f917b60258 Mon Sep 17 00:00:00 2001 From: Martijn Swaagman Date: Tue, 11 Aug 2026 22:55:20 +0200 Subject: [PATCH 3/3] fix(release): publish_only fallback selects newest draft, fails if none --- .github/workflows/release.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21d308db..7a6347cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -147,8 +147,14 @@ jobs: # points at it until every asset is attached — avoids a 404 window. TAG="${{ needs.release-please.outputs.tag_name }}" if [ -z "$TAG" ]; then - # publish_only retry path has no tag_name; drafts are listed here. - TAG="$(gh release list --limit 1 --json tagName --jq '.[0].tagName')" + # publish_only retry path has no tag_name: pick the newest *draft* + # only (never a published release) and fail fast if none exists. + TAG="$(gh release list --limit 30 --json tagName,isDraft \ + --jq 'map(select(.isDraft)) | .[0].tagName // empty')" + if [ -z "$TAG" ]; then + echo "No draft release found to publish." >&2 + exit 1 + fi fi gh release upload "$TAG" \ dist/gddy-*.tar.gz \