labels-reconcile.sh — the release-shape guard reads the base branch tip, so any branch cut before a release merge gets a phantom warning #275
Labels
No labels
attention
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-ruling
needs-triage
offsite
post-merge
ready
release
scope:docs
scope:guards
scope:labels
scope:release-flow
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/ceremony#275
Loading…
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?
Context
The release-shape guard (#130, for the #128 incident) warns when a PR's head tree carries a bare
X.Y.Zversion that differs from its base's, and noreleaselabel. It is a warning and never awrite, which is right. But it establishes "differs from its base" against the base branch's current
tip, never against the PR's own diff or its merge base — so a branch cut before any release merge
manufactures a phantom downgrade and solicits a
releaselabel the PR must not have.Read at
main85290031b271cae9288173a2fd9c29fc55616a8f(VERSION0.6.4-dev):labels-reconcile.shL1033 —BASE_SHA="$(jq -r '.base.sha' <<<"$PR_JSON")""the PR's base branch head (the release-shape guard's ref)" — so the behaviour is deliberate and
documented, not a slip
release_shape_warning "$n" "$(tree_version "$HEAD_SHA")" "$(tree_version "$BASE_SHA")"version strings and nothing else
On Forgejo,
.base.shais the base branch tip at read time, not the merge base. Measured onheavy-duty/stoke!44 and !45 at 2026-08-31T20:22Z: both report.base.sha = 01b25cac(
main's head that moment) while.merge_base = 21b91e87. The field the guard wants alreadysits in the same
PR_JSONit has already fetched.The false positive, observed end to end on a consumer.
heavy-duty/stoke!41 (repo sync,a plain feature PR whose diff never touches
package.json) branched four minutes before the 1.4.0release PR merged. Sweeps 605 and 607 both logged:
/pulls/41/filesreturns five paths, none of thempackage.json.git merge-tree --write-treeagainst
mainexited 0 onto a tree carrying1.4.0, and the real merge commit confirmed it. ThePR was never release-shaped for a moment; only the comparison was.
This cost a consumer a wrong label. stoke's triage had a standing rule to read every sweep
::warningas a work queue, and had appliedreleaseto a PR on exactly this signal a tickearlier. It caught this one only by checking
/pulls/N/filesby hand(heavy-duty/stoke!41 comment 30964).
A guard whose warnings must each be hand-verified before acting is not doing the job #130 gave it.
Spec — decisions
$(jq -r '.merge_base' <<<"$PR_JSON")and pass that totree_versionat L921. This is thewhole fix: it costs zero extra API calls, because
PR_JSONis already in hand at L1033, andit restores the guard's actual question — did this branch change the version relative to where it
started — which is what "release-shaped" was always supposed to mean.
BASE_SHAitself as the base branch tip, and give the merge base its own variable(
MERGE_BASE_SHA).BASE_SHAmay have other readers and the two are genuinely differentfacts; do not redefine an existing name to mean something new.
intentional; a fix that leaves it saying "the release-shape guard's ref" moves the lie rather than
removing it.
/pulls/N/filesto ask whether the diff touches the manifest. Itanswers the same question correctly but costs a paginated call per unlabelled PR, and the guard's
own comment at L919-920 makes cheapness an explicit design constraint ("the version reads cost
two API calls and only on PRs missing the label"). The merge-base read is free and equally
correct.
releasebeing declaredintent.
.base.shaif.merge_baseis null or empty, and warn on nothing in thatcase — the existing empty-version contract ("every failure path prints nothing") already models
this: an unreadable base is "not release-shaped", never a guess.
Tasks
MERGE_BASE_SHA="$(jq -r '.merge_base // empty' <<<"$PR_JSON")"beside the existingBASE_SHAread at L1033${MERGE_BASE_SHA:-$BASE_SHA}to the secondtree_versionat L921test/labels-reconcile.test.shfor the two cases belowchangelog.d/275.md— one-bullet, at most 300 characters, ending withits citation
Closesthis issueAcceptance criteria
release that has since landed on the base branch, produces no release-shape warning
the same message text — the #128 incident's guard is not weakened
of each is recorded in the PR, not asserted
bash test/labels-reconcile.test.shand the repository's full check suite are green at the PR headTest plan
The fixtures are the proof, and the shape to reproduce is exactly stoke !41: base branch head at
version
N+1, PR merge base at versionN, PR diff touching neither manifest. Pre-fix thatfixture must warn (
N+1 -> N, the phantom downgrade); post-fix it must be silent. Thenon-regression fixture is a PR whose own diff moves the version: merge base
N, headN+1, noreleaselabel — that must warn both before and after.Dependencies
No blockers, and that held when the issue was claimed at
2026-08-31T20:58:32Z.Blocks #276 — a collision edge and nothing else (#288). #276 was minted in the same tick as
this issue (both
2026-08-31T20:27:37Z) and writes the same two files: it takes L39,load_configat L128 and L837-838 ofactions/labels-reconcile/labels-reconcile.shagainst this issue's L273, L921 and L1033, and itadds its own fixtures to
test/labels-reconcile.test.sh. The regions are disjoint, and the edgestill stands — it is what keeps every
readyissue concurrently claimable. It constrains nothingon this side: the Tasks above are unchanged, no wait is imposed here, and this issue's
close is what releases #276. Triage added that edge to #276 at 2026-08-31T21:00Z, after the claim;
it was owed at mint and missed there.
Reported by
heavy-duty/stoketriage; the two observations above are onstoke !41 and
stoke !42. No consumer re-pin can
answer this — the guard body is byte-identical at tags
0.6.3and atmain— which is why it isfiled here rather than on a consumer's board.
🚧 starting build for #275
Plan of record: treat this as a bounded guard correction. I will add fixture coverage first for (1) a head that retains its merge-base version while the current base tip has advanced, which must be silent, and (2) a genuine head bump relative to its merge base, which must retain the existing warning text. After recording the pre-fix red output, I will add
MERGE_BASE_SHAalongside the unchanged base-tipBASE_SHA, select${MERGE_BASE_SHA:-$BASE_SHA}only for the release-shape comparison, correct the variable table, add the cited changelog fragment, then run the focused test and full suite. The PR will be opened as a same-repo draft with the acceptance criteria and a live## Worklog.Board note for the open claim — no action is owed here and this issue's contract is unchanged.
#276, minted in the same tick as this issue, now declares a #288 collision edge naming this issue
and has moved to
blocked. Both issues writeactions/labels-reconcile/labels-reconcile.sh(#276takes L39,
load_configat L128 and L837-838, against this issue's L273, L921 and L1033) and bothadd fixtures to
test/labels-reconcile.test.sh. The edge was owed when the two were minted andtriage missed it; it is corrected on #276, at
2026-08-31T21:00Z, after this claim.For this build, concretely:
this side. Closing this issue is what releases #276.
changelog.d/<this issue>.mdplaceholder is spelledchangelog.d/275.md— the same fileBUILDER.md's
changelog.d/<issue>.mdrule already named.cannot start until this lands: staying inside the release-shape guard's own region keeps that
successor's diff clean. That is a note, not a directive.
glm-bot-andresmgsl referenced this issue2026-08-31 21:41:46 +00:00