Make release.yml idempotent and tighten permissions#35
Merged
Conversation
The github-release step previously called `gh release create` unconditionally,
which fails with HTTP 422 when a release already exists for the tag - e.g.
when a maintainer publishes the release through the GitHub UI (which also
creates the tag), as happened for v0.1.1.
- Guard with `gh release view ... || gh release create ...` and upload `dist/*`
to the existing release with `--clobber` when present.
- `permissions: {}` at workflow level, escalated only where needed.
- Drop unused `attestations: write`; PEP 740 attestations are produced by
pypa/gh-action-pypi-publish v1.11+ from `id-token: write` alone.
- Add `concurrency: cancel-in-progress: false` on the tag ref.
- Verify the tagged commit is an ancestor of origin/main.
- Attach built sdist + wheel to the GitHub Release.
- Fold verify-version into build (no need for a separate runner).
Drop machinery that wasn't earning its lines: - The concurrency block - tag-push races are vanishingly rare for this repo. - UV_FROZEN env - irrelevant; uv build doesn't sync from the lockfile. - The tag-on-main ancestry check (and its fetch-depth: 0) - the tag-vs-pyproject match already catches the common error modes. - The if/else in github-release. Replace with the cleaner pattern of "create the release if missing, then always upload dist/* with --clobber." Idempotent on retry without branching. - --verify-tag and the post-publish info echo - noise. Use job-level env (GH_REPO, TAG) to drop --repo flags from each gh call.
- Add concurrency block matching ci.yml's group key. Use cancel-in-progress: false (unlike ci.yml which uses true) so an in-flight pypa/gh-action-pypi-publish never gets killed mid-upload. - Add env: UV_FROZEN: true to match the other uv-using workflows. - Name the final github-release step, matching the repo pattern of naming any step with a multi-line script.
guenp
approved these changes
May 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
github-releasestep inrelease.ymlcallsgh release createunconditionally, which returns HTTP 422 when a release already exists for the tag. This bit us on both 0.1.0 and 0.1.1: tagging via the GitHub UI (orgh release createlocally) creates the tag and the release atomically, so by the time the workflow runs the release exists and the step fails. PyPI publishing succeeded in both cases - only the GitHub-release step went red.This PR makes the workflow idempotent and trims it down (96 → 68 lines):
gh release view "$TAG" || gh release create "$TAG" --generate-notes, then unconditionallygh release upload "$TAG" --clobber dist/*. Works whether the release was created by the workflow or by hand.permissions: {};id-token: writeandcontents: writegranted only to the jobs that need them.attestations: write- it was unused. PEP 740 attestations have been default-on inpypa/gh-action-pypi-publishsince v1.11 and need onlyid-token: write.verify-versionintobuild(it was three lines of bash; the separate runner wasn't earning its keep).Test plan
actionlintandzizmorboth pass clean on the new file.dist/*attached).dist/*to the existing release instead of failing.Important
Most code in
ionq_core/is auto-generated and overwritten on regeneration.See CONTRIBUTING.md for which files are safe to edit.