Skip to content

Make release.yml idempotent and tighten permissions#35

Merged
splch merged 3 commits intomainfrom
fix/release-workflow-idempotent
May 1, 2026
Merged

Make release.yml idempotent and tighten permissions#35
splch merged 3 commits intomainfrom
fix/release-workflow-idempotent

Conversation

@splch
Copy link
Copy Markdown
Collaborator

@splch splch commented May 1, 2026

Summary

The github-release step in release.yml calls gh release create unconditionally, 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 (or gh release create locally) 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 unconditionally gh release upload "$TAG" --clobber dist/*. Works whether the release was created by the workflow or by hand.
  • Workflow-level permissions: {}; id-token: write and contents: write granted only to the jobs that need them.
  • Drop attestations: write - it was unused. PEP 740 attestations have been default-on in pypa/gh-action-pypi-publish since v1.11 and need only id-token: write.
  • Attach the built sdist + wheel to the GitHub Release (second copy alongside PyPI).
  • Fold verify-version into build (it was three lines of bash; the separate runner wasn't earning its keep).

Test plan

  • actionlint and zizmor both pass clean on the new file.
  • On the next tag push: confirm the workflow turns green end-to-end (PyPI publish + GitHub Release with dist/* attached).
  • Manually exercise the idempotent path: create a draft release in the UI for a future test tag, then push the tag, confirm the workflow uploads 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.

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).
@splch splch requested a review from a team as a code owner May 1, 2026 20:24
splch added 2 commits May 1, 2026 13:29
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.
@splch splch merged commit 1fdc036 into main May 1, 2026
12 checks passed
@splch splch deleted the fix/release-workflow-idempotent branch May 1, 2026 21:15
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.

2 participants