Skip to content

fix(release): publish the release through the REST API, not the gh CLI - #57

Merged
JoeyHoutenbos merged 10 commits into
mainfrom
fix/release-without-gh-cli
Oct 7, 2026
Merged

JoeyHoutenbos merged 10 commits into
mainfrom
fix/release-without-gh-cli

Conversation

@JoeyHoutenbos

@JoeyHoutenbos JoeyHoutenbos commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Why

release.yml runs on ${{ vars.CI_RUNNER || 'ubuntu-latest' }}. GitHub-hosted runners ship the GitHub CLI, but a self-hosted runner image cannot be assumed to. On one without it, the job pushes the annotated tag, then fails at gh release create with gh: command not found. That leaves a tag without a release, and a rerun refuses because the commit is already tagged.

What changed

  • packages/preset-nuxt4/templates/.github/workflows/release.yml: the one gh call becomes a curl POST to ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases, with the same fields: tag_name and name = the tag, target_commitish = the dispatched commit, generate_release_notes: true. It uses the job's GITHUB_TOKEN (contents: write, unchanged). Nothing else in the workflow used gh, so the job now needs only git and curl, on GitHub-hosted and self-hosted runners, in public and private repositories alike.
  • shared:github 3.1.0 → 3.1.1.
  • New test in packages/preset-nuxt4/test/github.test.ts: the emitted release.yml runs no gh command and posts to the releases endpoint.

Evidence

  • The new test fails on the old template and passes on the new one.
  • packages/preset-nuxt4: 25 files, 228 tests pass.
  • pnpm bump:check origin/main: OK.
  • The request body is valid JSON for a vX.Y.Z tag and a 40-character sha. Both are restricted character sets, which is why printf is enough here.
  • Exercised end to end in a generated project on a self-hosted runner without the GitHub CLI: before, the run pushed the tag and failed at gh release create; with this change, the run is green, the annotated tag points at the dispatched commit, and the release is published by github-actions[bot] with generated notes.

Joey Houtenbos and others added 3 commits October 2, 2026 12:06
The Release workflow runs on vars.CI_RUNNER when set, and a self-hosted
runner image cannot be assumed to ship the GitHub CLI. On such a runner
the job pushed the annotated tag and then failed at gh release create
with 'gh: command not found', leaving a tag without a release; a rerun
then refuses, because the commit is already tagged.

The step now POSTs to ${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases
with curl and the same fields (tag name as title, generated notes,
target = the dispatched commit), using the job's GITHUB_TOKEN with
contents: write. That works on GitHub-hosted and self-hosted runners,
public and private repositories alike, and the job needs only git and
curl. gh was used nowhere else in the workflow.

shared:github 3.1.0 -> 3.1.1. New test: the emitted release.yml calls
no gh command and posts to the releases endpoint (fails on the old
template).
- Drop `--fail-with-body` (curl >= 7.76): Ubuntu 20.04 and Debian bullseye
  ship older curl and would fail after the tag push. Check the HTTP status
  with `-w '%{http_code}'` instead, and print the API's error body on failure
  rather than sending it to /dev/null.
- `--retry 3` for transient errors, since any failure here leaves a pushed
  tag without a release and a rerun refuses the already-tagged commit.
- Add the `CI_RUNNER_APT_PACKAGES` step from lint-test.yml, before checkout so
  it can also provide git.
- Comments: drop the gh tool list from the emitter, and move the incident
  narrative out of the source per the comment policy.
- Test: match any `gh` word on command lines, and pin the printf format and
  its argument order.
…lure

- Pass the Authorization header to curl on stdin (`-H @-`): argv shows in
  `ps` for every user on a shared self-hosted runner.
- `|| status=000` so a transport failure (DNS, refused, TLS) still reaches
  the error branch instead of `set -e` ending the step silently.
- Word the error for a retried POST whose first attempt already created the
  release: "if the release is missing, create it from the tag".
- Docs: `CI_RUNNER_APT_PACKAGES` now applies to both workflows.
- On any non-201, check `GET /releases/tags/$TAG` before failing: a retried
  POST whose first attempt created the release gets 422 `already_exists`,
  which used to fail the run and then block the rerun on the existing tag.
- `|| true` instead of `|| status=000`: `-w` already prints 000 on a
  transport failure, and the override could mask a real status.
@JoeyHoutenbos
JoeyHoutenbos merged commit 1c31608 into main Oct 7, 2026
6 checks passed
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