Skip to content

D211 — The pre-release flag is set by the release job and verified there

Decision. A dedicated prerelease-flag job in .github/workflows/release.yml marks a v0.* release as a GitHub pre-release and then reads the flag back, failing the release if it is not set. A tag outside v0.* is asserted the other way round: a stable release must not carry the flag. "prerelease": true stays in release-please-config.json untouched, and is not the mechanism.

And install.sh stops asking for releases/latest. That endpoint is documented to skip pre-releases, so making the flag work would have broken the documented installation path — see Why the flag could not simply be switched on below. It now reads /releases?per_page=1, which is ordered newest-first and includes pre-releases.

D82 chose this marker and is still the right choice; this records that its implementation never worked and what replaced it.

Why. D82 says every GitHub release is marked Pre-release until 1.0, and CONTRIBUTING.md and the README badge both build on that. Measured:

tag isPrerelease
v0.1.0, v0.1.1 true
v0.2.0v0.12.3 false

The two that carry it are exactly the two D82 says were marked by hand ("operator action"). Every release produced by the pipeline since — twenty-one of them — is a plain release. So the configuration key was never the mechanism; the operator was, once, and then stopped being.

The failure is the shape this repository keeps finding: a claim that reads as implemented, a config key that looks like it does the job, and nothing anywhere comparing the two. The README badge is downstream of it — ?include_prereleases is justified in D82 by "with every release a pre-release, releases/latest would freeze on the last stable one", which has been false for a year, so the badge has been working for a different reason than the one written down.

Why the flag could not simply be switched on. install.sh resolved the version to install through GET /repos/{owner}/{repo}/releases/latest, and that endpoint excludes pre-releases by definition. Two failures follow from setting the flag without touching it:

  • retro-marking everything leaves no unflagged release at all, so the endpoint answers 404, latest_tag() comes back empty and the installer dies with "cannot determine latest release (rate-limited? set GITHUB_TOKEN)" — a message pointing at the wrong cause;
  • marking only future releases is worse, because it does not fail: the endpoint keeps answering with the last unflagged release, so curl … | sh silently installs v0.12.3 forever while releases keep coming.

This is also the likely reason the broken flag went unnoticed for a year: the installer works because nothing is flagged. The two decisions are coupled, so they land together — the fixture in test/install/lib/fake-curl.sh answers only the new path, which means a regression back to releases/latest fails make test-install rather than surfacing at a user's shell.

So the flag is verified, not just set. Setting it in the workflow would fix today and leave the same hole: a token scope change, a gh behaviour change, or a future release: mode: in .goreleaser.yaml that recreates the release could quietly undo it. Reading the flag back in the same job costs one API call and turns the next regression into a red release instead of a year of wrong metadata.

Alternatives rejected.

  • Delete "prerelease": true from release-please-config.json. It is inert for the GitHub flag, so removing it is tidier — and it is an input to the tool that computes the next version, changed immediately after cutting a release, with no way to test the effect short of cutting another one. Not worth it for tidiness. The key is documented as not being the mechanism instead, in docs/deployment.md §CI/CD, where a reader looking for the flag will be.
  • Abandon the marker and correct the prose to match reality. The cheapest option, and it throws away a decision that is sound: 0.x plus a visible pre-release badge is exactly what a pre-1.0 project should show, and D82's reasoning for preferring the flag over a -beta tag suffix (which would re-break go install @latest) still holds.
  • Make goreleaser own the flag with release: prerelease: true. mode: keep-existing is deliberate — the release and its notes belong to release-please (D-note in docs/deployment.md) — and giving goreleaser a say over release metadata invites it to recreate what it is supposed to leave alone.
  • A manual operator step at each release. That is what has been failing since v0.2.0.
  • Leave the twenty-one published releases as they are. Retro-marking them is what D82 already did for v0.1.x, and a release history where the flag appears twice at the beginning and then never again is worse than either consistent answer.

Consequences. The twenty-one releases from v0.2.0 to v0.12.3 were retro-marked as pre-releases (operator action, as in D82), so the whole published history is now consistent. releases/latest consequently resolves to nothing until 1.0.0: the README badge's ?include_prereleases becomes load-bearing rather than incidental, and any new link or script that reaches for releases/latest will fail — reach for /releases, or for /releases?per_page=1 when a script needs the newest tag. At 1.0.0 the job's condition stops matching on its own, which is one fewer manual step than D82's "at 1.0.0 the flag is removed"; the stable-tag assertion then starts guarding the opposite direction, and releases/latest starts working again on its own.