Skip to content

chore(ci): make release asset upload idempotent#10430

Merged
jdx merged 1 commit into
mainfrom
codex/idempotent-release-assets
Jun 14, 2026
Merged

chore(ci): make release asset upload idempotent#10430
jdx merged 1 commit into
mainfrom
codex/idempotent-release-assets

Conversation

@jdx

@jdx jdx commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • create GitHub releases without uploading assets in the initial create call
  • upload release assets one-by-one with --clobber so reruns can repair stale or partial assets
  • delete unexpected/incomplete assets and verify the final GitHub asset set before publishing

Root cause

A canceled release run can leave partial GitHub release assets behind, including starter assets. A later rerun may build and publish CDN assets successfully while GitHub Releases remains incomplete.

Validation

  • bash -n scripts/github-release-assets.sh
  • shellcheck scripts/github-release-assets.sh
  • ruby -e 'require "yaml"; YAML.load_file(".github/workflows/release.yml"); puts "yaml ok"'\n- git diff --cached --check\n\nThis PR was generated by an AI coding assistant.

Note

Medium Risk
Changes the release publish path for downloadable GitHub assets, but scope is CI-only with explicit verification and draft-only repair before the existing publish step.

Overview
Release workflow no longer creates the GitHub draft release and uploads all releases/$VERSION files in one gh release create call. It now runs scripts/github-release-assets.sh, which treats asset upload as a repairable step.

The script creates or updates a draft release (notes/title only), removes unexpected or non-uploaded assets on GitHub, uploads each local file with --clobber, then fails the job unless the remote asset names exactly match releases/$version. It also refuses to run if the tag’s release is already published, since those assets are immutable.

This addresses canceled runs that left partial GitHub release assets while later steps (e.g. CDN) could still succeed.

Reviewed by Cursor Bugbot for commit 2308ee6. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

Chores

  • Improved automated GitHub Release handling by introducing a dedicated release-asset management script that generates and verifies draft assets end-to-end.
  • Updated the release workflow to delegate draft creation and asset preparation to the new script while preserving the existing publish/finalize steps.
  • Added stricter checks to prevent missing, unexpected, or incomplete release assets before the release is completed.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a960023-a29c-4a2c-b60b-a29823f2d179

📥 Commits

Reviewing files that changed from the base of the PR and between 2702b59 and 2308ee6.

📒 Files selected for processing (2)
  • .github/workflows/release.yml
  • scripts/github-release-assets.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/release.yml

📝 Walkthrough

Walkthrough

The inline gh release create --draft step in the release workflow is replaced by a call to a new script scripts/github-release-assets.sh. The script idempotently creates or updates a GitHub draft release, deletes stale or incomplete remote assets, uploads all local assets with --clobber, and verifies the final remote asset state, exiting non-zero on any mismatch.

Changes

GitHub Release Asset Management

Layer / File(s) Summary
Script initialization, asset enumeration, and workflow wiring
scripts/github-release-assets.sh, .github/workflows/release.yml
Strict Bash mode, required env-var and directory validation, expected asset filename enumeration; workflow step replaced from inline gh release create --draft glob invocation to a single call to the new script.
Release create-or-update logic
scripts/github-release-assets.sh
Queries the GitHub API for an existing release by tag; edits title and notes if found as a draft, or creates a new draft release with --verify-tag if not found; rejects published releases; retrieves the numeric release ID for subsequent operations.
Stale asset cleanup, upload, and post-upload verification
scripts/github-release-assets.sh
Fetches remote assets and deletes any not in the expected set or with state other than uploaded; uploads all local files with --clobber; re-fetches remote assets and asserts no missing, incomplete, or unexpected entries, printing ::error:: messages and exiting non-zero on failure.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 A script is born, so lean and mean,
The draft release it tends with care.
Old assets fade, new files appear,
Each upload verified, crystal clear—
The release bunny hops away with cheer! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the main change: making release asset upload idempotent through a new script and workflow modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@jdx jdx force-pushed the codex/idempotent-release-assets branch from 2702b59 to 2308ee6 Compare June 14, 2026 07:50
@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the single gh release create call (which bundled asset upload into creation) with a new script (scripts/github-release-assets.sh) that separates creation from asset management, making the release step idempotent and repairable after a partial failure.

  • The script creates or edits the draft release, prunes any stale/incomplete assets from a previous run, uploads all local release files with --clobber, and verifies the live GitHub asset set exactly matches the local directory before proceeding to CDN publish.
  • The RELEASE_TITLE env var set via $GITHUB_ENV in the preceding step is correctly inherited; the check-release gate and the script's own draft-check together prevent accidentally clobbering an already-published release.

Confidence Score: 4/5

CI/release automation only; the logic is sound and the new guards (draft check, stale-asset pruning, post-upload verification) make the process more robust than before.

The overall approach is correct and the three-phase lifecycle (create/edit → prune → upload → verify) addresses the root cause cleanly. The two findings are a portability nit (find -printf GNU-only) and a minor redundant API call; neither affects correctness in CI. The script is safe to merge.

scripts/github-release-assets.sh — the find -printf portability issue is worth a quick look before someone tries to run the script locally on macOS.

Important Files Changed

Filename Overview
scripts/github-release-assets.sh New script that manages GitHub release asset lifecycle idempotently: creates/edits draft, prunes stale assets, uploads all with --clobber, then verifies the final asset set matches local files. Minor: uses GNU-only find -printf and makes a redundant API call for release_id when the release already exists.
.github/workflows/release.yml Replaces inline gh release create (with assets glob) with a call to the new script; RELEASE_TITLE is propagated correctly via $GITHUB_ENV from the previous step.

Fix All in Claude Code

Reviews (1): Last reviewed commit: "chore(ci): make release asset upload ide..." | Re-trigger Greptile

actual_assets="$(mktemp)"
trap 'rm -f "$expected_assets" "$actual_assets"' EXIT

find "$release_dir" -maxdepth 1 -type f -printf "%f\n" | sort >"$expected_assets"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 find -printf "%f " is a GNU extension that isn't available on macOS/BSD find. If this script is ever run locally on a macOS developer machine (e.g., to debug a release), it will silently output nothing, causing the "release directory has no assets" guard to fire or producing an incorrect empty expected-assets list. A portable equivalent keeps the same semantics.

Suggested change
find "$release_dir" -maxdepth 1 -type f -printf "%f\n" | sort >"$expected_assets"
find "$release_dir" -maxdepth 1 -type f -exec basename {} \; | sort >"$expected_assets"

Fix in Claude Code

--draft
fi

release_id="$(gh api "repos/$repo/releases/tags/$version" --jq ".id")"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 When the release already exists (the if branch), its JSON is already in $release_json, so release_id can be read from the cached file instead of making a third identical API call. The extra round-trip is harmless but unnecessary.

Suggested change
release_id="$(gh api "repos/$repo/releases/tags/$version" --jq ".id")"
if [[ -s "$release_json" ]]; then
release_id="$(jq -r ".id" <"$release_json")"
else
release_id="$(gh api "repos/$repo/releases/tags/$version" --jq ".id")"
fi

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

@jdx jdx enabled auto-merge (squash) June 14, 2026 07:56
@jdx jdx disabled auto-merge June 14, 2026 07:58
@jdx jdx merged commit ce4be0a into main Jun 14, 2026
34 checks passed
@jdx jdx deleted the codex/idempotent-release-assets branch June 14, 2026 07:59
@github-actions

Copy link
Copy Markdown

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.7 x -- echo 18.2 ± 0.7 16.7 21.4 1.00
mise x -- echo 19.0 ± 1.3 17.3 40.6 1.04 ± 0.08

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.7 env 18.3 ± 0.8 16.6 22.4 1.00
mise env 18.5 ± 0.8 17.1 22.1 1.01 ± 0.06

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.7 hook-env 18.7 ± 0.8 17.0 23.2 1.00
mise hook-env 19.4 ± 0.7 17.7 22.1 1.03 ± 0.06

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.6.7 ls 15.7 ± 0.6 14.3 18.9 1.00
mise ls 16.3 ± 0.7 14.9 20.1 1.04 ± 0.06

xtasks/test/perf

Command mise-2026.6.7 mise Variance
install (cached) 133ms 135ms -1%
ls (cached) 59ms 59ms +0%
bin-paths (cached) 65ms 64ms +1%
task-ls (cached) 124ms 124ms +0%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant