fix(release): an ABSENT goreleaser config is not a config that parses (#1194) #1207
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!1207
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1194-absent-config-is-not-parses"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #1194.
Intended-targets: #1194
goreleaser checkreturnsrc=0both when the config parses and when there is none — it falls back to built-in defaults and reports success. The guard read only the exit code, so a tag predating the release pipeline was reported as one whose config is fine, and the operator was sent to fix a file that does not exist.🔑 The discriminator was already being printed and was not being read.
The design call: read
path=, do not test for the fileThe obvious repair is
[ -f .goreleaser.yaml ]. Measured, that is wrong — goreleaser accepts four default names, and each is reported through the same field:(rc=1 across that table is my deliberately-minimal probe config failing validation; the column that matters is
path=.)A
[ -f .goreleaser.yaml ]test reports ABSENT for a repo using the.ymlspelling — a fresh instance of this same defect.path=is goreleaser's own answer about which file it resolved, and that search list is goreleaser's to change, not ours. Where Y would be right instead: if goreleaser ever stopped printingpath=, or if we passed--configexplicitly and owned the resolution ourselves.Four states, not two
⚠️ The fourth exists because of the third. A goreleaser that stops printing
path=must not silently render as "this tag has no config" — that is#1194again, one version upgrade later. It refuses instead.📌 The pass message now names the file (
.goreleaser.yaml at v0.57.0 parses) rather than asserting that one exists, so a reader can see what was graded.An existing arm broke, and was repaired rather than relaxed
#980's arm keys on the invocation rather than the substring — deliberately, because this step's own success message contains the words "goreleaser check", and its comment records that a mutation replacing the call withif truehad left a substring needle green. Moving the call into a command substitution broke that needle.The needle now strips comment and
echolines, then matches a command position (line start, afterif, inside$(, with optionalVAR=VALprefixes). ✅ Re-proved against the exact mutation it was written for: M3 below reddens it.🔴 A mutation found an inert assertion in MY OWN new arm
M5 changed "This is NOT a config-migration problem" into "This may be a config-migration problem" — the precise inversion of AC2 — and the suite stayed green.
My arm tested
"config migration" in outwith a space, while the text is hyphenated. It never matched the baseline, so it could not have caught a regression either. Replaced with the two substantive halves of AC2: the absent case must name what is true (predates the release pipeline) and must not hand over the parse branch's remedy (older goreleaser/config migration on a re-tag). M5-redux and M6 now redden.Mutation verification — 7 mutants, each in isolation
path=read as ABSENTif true#980's pre-existing armM3 is the one that matters most, because it proves I did not weaken an existing guard while making its needle match my change. M5 is the one worth reading, because the arm that found it was mine and it was inert.
The arm runs the SHIPPED bytes
The step's
run:block is extracted from the YAML and executed against a shimmedgoreleaser— not a copy of the logic. That is the#1163lesson: an arm that grades a helper stays green while the shipped wiring is broken.⚠️ The shim's blind spot, stated because it is real: the stub reproduces goreleaser's observed output format. If a future goreleaser changes it, this arm still passes while production changes behaviour. That is exactly what the could-not-grade branch is for, and it is armed (M2) rather than assumed.
What this PR does NOT do
v0.34.0releases rebuildable. Nothing can — they predate the pipeline and never had assets. This makes the run say so instead of reporting a config that parses.#1197(the rebuild deletes assets then fails verify on every pre-signature tag). Different defect, different tracker, and#980is blocked on that one rather than this one.v0.33.0andmain) and are in#1194.goreleaser.ymldeclares onlypushandworkflow_dispatch, so it is not referenceable and correctly has none —gitea-twin --checkrc 0 is honest here rather than vacuous.📌 A note on
ac-closure-check, now that it is REQUIREDThis PR went red on it, correctly:
#1194's three ACs were unticked. They are ticked now with evidence.⚠️ But ticking them did not clear the gate.
ac-closure-checkreads the issue's AC state and re-runs only on pull_request events, so an issue edit cannot re-fire it. The status stayedfailureat its original timestamp while the condition it reports had already been fixed. Clearing it needs a touch on the PR itself — this edit is that touch.That was harmless while the context was advisory. Now that it blocks a merge, a PR can sit red on a condition that no longer exists, and the remedy (edit the PR to re-fire a gate about a different object) is not discoverable from the failure message.
Gates
go build·go vet·go test ./...·gofmt·gitea-twin --check·fragment-check(rc 0, zero warnings on my fragment) · 8 bats suites (0 not-ok) ·contract-paths-check·workflow-parse-check·dated-examples-check— every rc captured directly, never through a pipe. Re-run after rebasing onto2352188.📌 Opened after the promotion of 13 advisory contexts to required (
#1177), so this is among the first PRs facing 26. All the newly-required gates were run locally above.🤖 Generated with Claude Code
https://claude.ai/code/session_01MMmaXmMhZdAAnttWBS6zqa
APPROVED at
9b5dbe5d. All three questions answered yes, and the second one is answered better than it was asked.① It reads goreleaser's own answer, and says why a file test would be a new instance of the same bug
The comment makes the case I would have made in review:
goreleaseraccepts four default names (.goreleaser.yaml/.yml,goreleaser.yaml/.yml), "and that list is goreleaser's to change, not ours" — so a[ -f .goreleaser.yaml ]probe reports ABSENT for a repo using the.ymlspelling. That is#1194again with a different trigger, and it is named before anyone proposes it.📌 And
path=missing entirely is graded COULD-NOT-GRADE rather than absent — "a goreleaser that stops printing it must not silently render as 'this tag has no config'". Three states, and the third is the one that keeps the fix from expiring on a tool upgrade.② The predicate is not version-keyed — and I checked the MESSAGE too, which is where it usually survives
The branch tests
[ -z "$cfg" ]. No version comparison anywhere in the guard. But the operator-facing text makes a version-ordered claim, so I measured it across all 113 tags:The claim holds. ".goreleaser.yaml first appears at v0.34.0; an empty release below that line never had assets" is true of every tag in the repository.
🔴 And the example in the review brief is not:
v1.0.0-alpha.0DOES carry a config.It sorts top and it is old — that half of the caution is right, and it is exactly why a version-keyed predicate would be wrong here. But it is not a config-less tag, so it is not an instance of the hazard it was offered as. The hazard is real and this artefact is not its example.
⚠️ My own first pass at that measurement produced fourteen false positives — a glob
v0.[0-2]*|v0.3[0-3]*that does not excludev0.3.0throughv0.9.0. It printedv0.3.0 <- ABOVE the line, which is visibly absurd, and that is the only reason I caught it. Version ordering is not lexical and a glob cannot express it;sort -Vand a position index can.③ The absent case names what is true
"This tag predates the release pipeline… An empty release below that line never had assets, so nothing was lost and nothing can be restored. This is NOT a config-migration problem: there is no config to migrate." — and the last clause is the one that repairs the original defect, which was pointing an operator at a migration that is a no-op.
Mutations, run by me
M3 is the one I would keep: it reddens on the exact repair a future editor would reach for, which is what makes the comment enforceable rather than advisory. And the arm executes the shipped
run:block rather than grepping the YAML.What this approval does not cover
The guard has not run against a real absent-config tag in CI — the arm drives the extracted block with a shimmed
goreleaser. The first real evidence is a rebuild attempt on a pre-v0.34.0tag.Only
goreleaser check's output is graded. If a future goreleaser printspath=for a config it did not actually use, this reads it as authoritative — which is the correct trade, and it is the assumption to revisit if the tool's output shape ever changes.