Skip to content

Use gh release create instead of ghalactic action - #212

Open
garrison wants to merge 3 commits into
mainfrom
worktree-gh-release-create
Open

Use gh release create instead of ghalactic action#212
garrison wants to merge 3 commits into
mainfrom
worktree-gh-release-create

Conversation

@garrison

@garrison garrison commented Aug 20, 2026

Copy link
Copy Markdown
Member

This PR drops a dependency on a third-party GitHub Action in the release workflow. It follows the example of qiskit-quimb, which creates the release using the gh tool.

This PR was generated by Claude Opus 5.


Replaces ghalactic/github-release-from-tag@v6 with a plain gh release create call.

Why

The ghalactic action is actively maintained, so this isn't a migration away from something broken. The reason is that we were using almost none of it.

Its headline feature is parsing a rich annotated tag message into the release title and body. Our tags don't have one:

$ git tag -l --format='%(contents)' 0.4.0
0.4.0

So the tag-message parsing, the .github/github-release-from-tag.yml config file, and the discussion/reaction features were all dormant. What we actually used was "attach dist/*, and ask GitHub to autogenerate the notes" — which gh does directly, with no third-party action.

That matters most in this job specifically, because it is the one that holds a token with contents: write.

Changes

  • ghalacticgh release create. gh is preinstalled on GitHub-hosted runners. --generate-notes reproduces generateReleaseNotes: "true"; a non-prerelease is the default, so prerelease: false needs no equivalent.
  • Explicit permissions: contents: write. The job previously declared no permissions and inherited the repository default, which is write across all scopes. Now it's scoped to what a release actually needs.
  • checksums.sha256 kept. The action generated this automatically and it's present on existing releases, so it's now produced by a sha256sum step rather than silently disappearing.
  • checksums.json dropped. Also generated by the action. It's the same data as the .sha256 file in a different format; happy to add it back if it's used somewhere.
  • Checkout tag step removed. It existed because the action read the tag annotation from a local clone. gh goes through the API, so no checkout is needed.
  • --verify-tag added. Without it, gh release create will create a missing tag. This makes the job fail instead, which is the behavior we want for a tag-triggered release.
  • --title '${{ github.ref_name }}' is technically redundant with --generate-notes, but pins the title to the bare version to match the existing releases (e.g. 0.4.0).

Second commit: publish to PyPI before the GitHub release

A separate fix, prompted by the PyPI job having failed on a past release.

A PyPI upload is irreversible — a version can be yanked, but the filename can never be reused. A GitHub release is fully reversible — delete it, fix, recreate. The old ordering (pypi needs github) gated the irreversible step on the reversible one, which is backwards.

The partial-failure matrix makes the case:

Scenario Old order (githubpypi) New order (pypigithub)
First step fails GitHub release fails; PyPI never runs. Delete the release, re-run. Recoverable. PyPI fails; nothing else ran. Nothing to clean up. Recoverable.
First succeeds, second fails Release page advertises a version that isn't installable. A re-run can't self-recover: the github job fails on the already-existing release before pypi is reached. Package is installable; only the release page is missing. Re-run, or create it by hand. User-facing artifact is already correct.

If exactly one of the two succeeds, it should be the one that can't be redone. PyPI is also the step with more moving parts — OIDC trusted publishing plus the pypi environment gate — which is presumably why it's the one that has failed before.

The job blocks are also swapped in the file so it reads in execution order, and the pypi job's unused Checkout tag step is dropped (it publishes downloaded artifacts and never reads the repository).

Verification

actionlint passes clean on the workflow, including its shellcheck pass over the new run: steps, and the job graph resolves with no cycle (pypibuild_*, githubbuild_* + pypi).

The release path itself can't be exercised without pushing a tag, so the first real test will be the next release — the failure modes to watch are the token/permission pair and the dist/* glob.

The ghalactic action's main feature is turning a rich annotated tag
message into the release title and body. Our tags carry only the bare
version as their message, so that never applied; the action was only
being used to attach `dist/*` and request GitHub's autogenerated notes,
both of which `gh` does directly.

Replacing it removes a third-party action from the one job that holds a
token with `contents: write`, and lets that permission be scoped
explicitly rather than inherited from the repository default of `write`.

`checksums.sha256` was generated by the action, so it is now produced by
a `sha256sum` step to keep it on the release. The action's
`checksums.json` is dropped. The `Checkout tag` step is removed since
`gh` reads the tag through the API rather than from a local clone, and
`--verify-tag` makes it fail rather than create a tag that is missing on
the remote.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coveralls

coveralls commented Aug 20, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 32528232949

Coverage remained the same at 96.524%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 1640
Covered Lines: 1583
Line Coverage: 96.52%
Coverage Strength: 0.97 hits per line

💛 - Coveralls

A PyPI upload is irreversible: a version can be yanked, but the filename
can never be reused. A GitHub release, by contrast, can be deleted and
recreated freely. The previous ordering gated the irreversible step on
the reversible one.

That ordering had a bad partial-failure mode. If the GitHub release
succeeded and PyPI then failed, the repository advertised a release whose
version was not installable, and re-running the workflow could not
recover on its own, because the `github` job would fail on the
already-existing release before `pypi` was reached.

With the order flipped, a PyPI failure leaves nothing to clean up, and a
GitHub failure leaves the installable package in place with only the
release page missing, which can be created by hand or by a re-run.

Also drops the unused `Checkout tag` step from the `pypi` job; it
publishes downloaded artifacts and never reads the repository.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
garrison added a commit to Qiskit/sbd-eigensolver-python that referenced this pull request Aug 20, 2026
This is based on the revised workflow at
Qiskit/qiskit-addon-utils#212.
@garrison

Copy link
Copy Markdown
Member Author

A modified version of this workflow was used to generate the first release of sbd-eigensolver-python: https://github.com/Qiskit/sbd-eigensolver-python/releases/tag/1.6.0

So I think it is safe to merge here and use for the next release of qiskit-addon-utils.

@garrison
garrison marked this pull request as ready for review August 20, 2026 18:52
@garrison garrison added the ci-cd Related to CI/CD pipeline label Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-cd Related to CI/CD pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants