ci: harden release artifact uploads (#18516)

Serialize final release jobs and delete old assets before retagging to
avoid overlapping updates and mixed-version payloads.

Keep uploads parallel and retry failures twice with 15s/30s delays.
Retry cleanup using a fresh asset list and upload the checksum manifest
only after all payloads succeed.
This commit is contained in:
Daniel Hiltgen
2026-09-17 16:28:46 -07:00
committed by GitHub
parent dfabde4539
commit 511324245f
+45 -9
View File
@@ -749,6 +749,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: release environment: release
needs: [darwin-build, windows-app, docker-build-push] needs: [darwin-build, windows-app, docker-build-push]
concurrency:
group: release-publish
queue: max
cancel-in-progress: false
permissions: permissions:
contents: write contents: write
env: env:
@@ -784,28 +788,59 @@ jobs:
- name: Generate checksum file - name: Generate checksum file
run: find . -type f -not -name 'sha256sum.txt' | xargs sha256sum | tee sha256sum.txt run: find . -type f -not -name 'sha256sum.txt' | xargs sha256sum | tee sha256sum.txt
working-directory: dist working-directory: dist
- name: Create or update Release for tag - name: Publish release artifacts
shell: bash
run: | run: |
RELEASE_VERSION="$(echo ${GITHUB_REF_NAME} | cut -f1 -d-)" retry() {
local attempt
for attempt in 1 2 3; do
if "$@"; then
return 0
fi
if [ "$attempt" -lt 3 ]; then
echo "::warning::Attempt $attempt failed; retrying in $((attempt * 15))s: $*"
sleep "$((attempt * 15))"
fi
done
echo "::error::Failed after 3 attempts: $*"
return 1
}
delete_assets() {
local assets asset
# Re-list on each attempt: a failed DELETE may still have removed the asset.
assets=$(gh api --paginate "repos/${GITHUB_REPOSITORY}/releases/$1/assets" --jq '.[].url') || return 1
while IFS= read -r asset; do
[ -z "$asset" ] && continue
gh api --method DELETE "$asset" || return 1
done <<< "$assets"
}
RELEASE_VERSION="${GITHUB_REF_NAME%%-*}"
echo "Looking for existing release for ${RELEASE_VERSION}" echo "Looking for existing release for ${RELEASE_VERSION}"
OLD_TAG=$(gh release ls --json name,tagName | jq -r ".[] | select(.name == \"${RELEASE_VERSION}\") | .tagName") OLD_TAG=$(gh release ls --json name,tagName | jq -r --arg version "$RELEASE_VERSION" '.[] | select(.name == $version) | .tagName')
if [ -n "$OLD_TAG" ]; then if [ -n "$OLD_TAG" ]; then
RELEASE_ID=$(gh release view "$OLD_TAG" --json databaseId --jq .databaseId)
echo "Deleting old assets from ${OLD_TAG}"
retry delete_assets "$RELEASE_ID"
echo "Updating release ${RELEASE_VERSION} to point to new tag ${GITHUB_REF_NAME}" echo "Updating release ${RELEASE_VERSION} to point to new tag ${GITHUB_REF_NAME}"
gh release edit ${OLD_TAG} --tag ${GITHUB_REF_NAME} gh release edit "$OLD_TAG" --tag "$GITHUB_REF_NAME"
else else
echo "Creating new release ${RELEASE_VERSION} pointing to tag ${GITHUB_REF_NAME}" echo "Creating new release ${RELEASE_VERSION} pointing to tag ${GITHUB_REF_NAME}"
gh release create ${GITHUB_REF_NAME} \ gh release create "$GITHUB_REF_NAME" \
--title ${RELEASE_VERSION} \ --title "$RELEASE_VERSION" \
--draft \ --draft \
--generate-notes \ --generate-notes \
--prerelease --prerelease
fi fi
- name: Upload release artifacts
run: | shopt -s nullglob
pids=() pids=()
for payload in dist/*.txt dist/*.zip dist/*.tgz dist/*.tar.zst dist/*.exe dist/*.dmg dist/*.ps1 dist/*.sh ; do for payload in dist/*.txt dist/*.zip dist/*.tgz dist/*.tar.zst dist/*.exe dist/*.dmg dist/*.ps1 dist/*.sh ; do
# Publish the checksum manifest only after every payload has succeeded.
[ "$payload" = dist/sha256sum.txt ] && continue
echo "Uploading $payload" echo "Uploading $payload"
gh release upload ${GITHUB_REF_NAME} $payload --clobber & retry gh release upload "$GITHUB_REF_NAME" "$payload" --clobber &
pids+=($!) pids+=($!)
sleep 1 sleep 1
done done
@@ -821,4 +856,5 @@ jobs:
echo "One or more uploads failed" echo "One or more uploads failed"
exit 1 exit 1
fi fi
retry gh release upload "$GITHUB_REF_NAME" dist/sha256sum.txt --clobber
echo "done" echo "done"