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.0 … v0.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 … | shsilently installsv0.12.3forever 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": truefromrelease-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, indocs/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
-betatag suffix (which would re-breakgo install @latest) still holds. - Make goreleaser own the flag with
release: prerelease: true.mode: keep-existingis deliberate — the release and its notes belong to release-please (D-note indocs/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.