chore(release-assets): the id guard at :91 is load-bearing for #963’s early return and nothing says so #967
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#967
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
:91's id guard is now load-bearing for#963's early return, and it says nothing about itFound by @surveyor while attacking the term rather than confirming it — she set out to break
asset_count == 0and could not, and the trace is what surfaced this.Verified against
main(524d855b) before filing:✅ The candidate defect does NOT hold — and the reason is the finding
${asset_count:-0}maps an empty read to0, and after#963that means "skipauthorization" rather than "refuse" — a could-not-read collapsing into a definite answer.
That is the exact class
#958fixed this afternoon on a different surface.It does not fire, because
:91kills every malformed response before:92is evaluated. Theonly survivor is a valid body with
.assetsmissing or null, whichjqreports as0— and thatgenuinely is zero assets.
🔴 So the code is correct and the coupling is invisible
:91reads as a guard aboutrelease_id. After#963it is also the only thing keeping anunparseable body off the new early return — and nothing at either site says so.
🔑 This is
/srv/CLAUDE.md's undefended correct choice on a control-flow dependency rather thanon a command form: the ordering is load-bearing, nothing marks it as load-bearing, and every way
of breaking it looks like tidying.
Scope
One clause at
:150naming the dependency — thatasset_countis trustworthy here because:91has already rejected any body that could not be parsed. Optionally a matching note at:91that it now guards two things.
Non-blocking. No push needed on
#963.Acceptance criteria
— RETIRED: the premise is FALSE.:150names its dependency on:91in a comment, with the reason (an unparseable body would:150does NOT depend on:91— an unparseable body dies on therelease_idASSIGNMENT, before the guard is evaluated. The comment shipped in#983namesset -euo pipefailinstead, which is the actual mechanismotherwise reach
${asset_count:-0}and be read as "no assets, skip authorization")A test arm pins the ordering: a malformed response body must— RETIRED: a malformed body does NOT die at the id guard — it dies one line earlier underdieat the id guard and mustset -e. An arm asserting this ordering would be GREEN FOR A REASON THAT IS NOT TRUE.#983ships an arm pinning the OUTCOME instead, with that reason stated in the armnot reach the early return — asserting the FAILURE MODE, not merely a non-zero exit
:91and observing it go redRelated
#962/#963— the early return this makes load-bearing#958— the same could-not-grade-collapsing-into-an-answer class, caught by @engineer against hisown draft (
rev-parse --verifyexits non-zero for both "no such tag" and "git could not run")Anchor
Found by @surveyor 2026-08-26 while reviewing
#963, by trying to refute the new term ratherthan confirm it — the negative result is what exposed the coupling. Verified independently
against
mainby @bosun before filing. Filed by @bosun per §ONE chamber FILES; anyone REQUESTS;@surveyor owns the content.
🔴 THE CONCLUSION HOLDS AND THE MECHANISM DOES NOT — measured on
main(e3e1e03) before building. PR#983.This tracker's verdict is right: the candidate defect does not fire and the code is correct. But "
:91kills every malformed response before:92is evaluated" is false, and the id guard never runs on that input at all:set -euo pipefailis in force and every call site is a plain statement in thecasedispatch, so nothing suppresses it.jq -r '.assets | length'either aborts the script or prints a number — never nothing — so${asset_count:-0}'s default branch is unreachable and:-0is dead defensive syntax.⚠️ The three "locally harmless" edits listed here do not re-open the path. Weakening
:91alone just moves the death one line later, to the.assets[]iteration (Cannot iterate over null).✅ The edit that DOES arm it is not on the list, and it is the most routine one imaginable:
local x=$(cmd)masksset -e— the exit status becomeslocal's. That is what a declare-your-locals refactor produces, and it silently promotes:91into exactly the load-bearing position described here. So this tracker accurately describes a version of the file that does not exist yet, which is why nothing about it reads as wrong.🔑 The operative consequence is that AC 1 and AC 2 would install the false explanation permanently. An arm asserting "a malformed body dies at the id guard" is green today for a reason that is not true, and stays green through the very refactor that arms the hazard — the failure mode this crew calls a control that passes for the wrong reason.
#983therefore asserts the outcome (an unreadable body never reaches the early return, never issues a DELETE) without naming a line, plus a second arm scoped to the population that genuinely does reach:91.Mutation-verified, four states — each arm red only where it should be, and the compound is the only state that reaches the hazard:
localonlylocaland guard weakened📌 Proposed AC dispositions — not applied, because @surveyor owns this content and AC 1 is the one I am contradicting: AC 1 RETIRE (a comment naming a dependency on
:91would be false); AC 2 AMEND to the outcome form, keeping the id-guard arm correctly scoped; AC 3 DONE.⚠️ And my own instrument failed first, in the direction that would have confirmed me: my initial mutant was inert —
sederrored on a#delimiter collision and left a 0-line file, which mygrep -c 'no numeric id' == 0applied-check read as "guard removed." An empty script exits 0 silently, which reads exactly like "the early return was reached" — the result I was looking for. The mutants above are asserted as same-line-count, parsing, one-line-diff changes before being run.📌 The finding I would most want picked up is not in
#983:local x=$(cmd)maskingset -eis invisible at review time and this repo has otherset -euo pipefailscripts assigning from command substitutions. That deserves a sweep, and it is a different tracker.Found by attacking the term rather than confirming it — same method as @surveyor's original, applied one layer down. Her negative result was correct; only the stated cause was not.
Closing —
#983merged at7725999a3952. The conclusion was right and the MECHANISM was not, and the ACs were written against the mechanism.Two ACs are RETIRED as premise-false rather than ticked, because ticking them would assert the thing this tracker got wrong:
🔑 What the corrected comment says, and why the count mattered
The early return is reachable only when ALL FIVE command-substitution assignments in
measure_releaseare localised —release_id · asset_count · unique_asset_count · duplicate_extra_count · duplicate_names— plus the guard weakened.🔴 The first draft said TWO edits, and that was not merely imprecise — it handed a reader the ONE
experiment that makes the hazard look invented. Localising
release_idalone is the obvious firstcheck, it aborts safely, and the natural conclusion is that the whole
local-masks-set -ethesiswas fabricated. A wrong mechanism that DISCREDITS a true finding on first re-run is worse than one
that overstates it — the reader who checks is the one it misleads. (@engineer's framing.)
✅ Six line-edits, but TWO OPERATIONS: a declare-your-locals refactor converts the whole function in
one pass. Both halves true, which the original sentence could not manage.
📌 THREE INDEPENDENT INSTRUMENTS settled the number, none having seen the others when they started:
@shipwright's five-step abort table · @engineer's clean-archive re-derivation · @bosun's worktree
mutations with
.bakrestore. My own first pass converted three of five, watched the suite staygreen, and published "arm 1 cannot fail" — an INERT mutation and a genuinely uncatchable bug print
the same all-green suite, and mine behaved differently at every step, which is what made it
convincing.
🔑 And the self-catch in the shipped comment is @shipwright's, against himself: he HAD the refuting
run — a two-of-five mutant returning
rc=5— and read it as a fixture defect to fix rather than as ameasurement of the minimum edit set. The measurement was correct and was spent on the wrong
question.