labels/scope clobbers a label written while it runs — the ceremony PR can silently lose release
#130
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/ceremony#130
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
On #128 — the
0.2.0ceremony PR — thereleaselabel was set by the builder and removed 2 seconds later bygithub-actions[bot]. Nothing reported it; I found it doing board hygiene, and it was restored by hand.The timeline (
GET /repos/heavy-duty/ceremony/issues/128/timeline):The only actor in that window is
labels / scopefrom run 30089306911, whose job ran11:22:00 → 11:22:07; the builder's write landed mid-job. The removal shares its second and its actor with the labeler's own two writes.labels / reconcilein that same run was cancelled and wrote nothing, and no other workflow touched labels in that window.The control case is #129, the duplicate ceremony PR:
releaseapplied at 11:23:55, its labels run started 11:23:58, scopes written 11:24:01,releasenever removed. Label present before the job starts → preserved. Label written while the job runs → clobbered.That is a read-modify-write race, and the surprising part is that it happens at all:
.github/workflows/labels.yml:42-46setssync-labels: falseand the step log confirms it, with the comment "additive only — a hand-applied scope must survive the machine". Whateveractions/labeler@v5does on this path is not purely additive. Pinned to SHA8558fd74291d67161a8a78ce36a881fa63b766a9in that run.Why this is not cosmetic.
releaseis the merge door's declared-intent read (lib/facts.sh). Lose it andlabeled='no', decide takes the refuse row, and the ceremony merge goes red onmainpublishing nothing — on a PR whose every other check is green, from a label removed a minute and a half earlier. The window is widest at PR open, which is exactly when a ceremony PR gets labeled. This is drill probe (c) firing on the canonical repo instead of the scratch one, and its trace is a timeline entry nobody reads.Same shape as
box#108and the guards this repo already bought: the failure leaves no trace and the one signal anyone would trust is absent exactly when the result is wrong.Spec
Diagnose first, then fix — the mechanism is not yet established and the fix depends on it.
release(or any non-scope label) written whilelabels / scopeis in flight. Confirm the clobber and capture whether labeler issuesPUT /labels(set) orPOST /labels(add) — the API call it makes is the whole answer.actions/labeler@v5sets rather than adds despitesync-labels: false, replace the step with a direct additivePOST /issues/{n}/labelsof the derived scopes — the config parse is the only part worth keeping, and this repo already computes label sets in shell elsewhere;labels.ymlcomment at :44-46 becomes true or goes away. "Additive only — a hand-applied scope must survive the machine" is currently a claim the behavior contradicts. A comment that lies is worse than none.releaseis a state the board should refuse rather than discover at merge.labels-reconcilealready sweeps every open PR and already knows a PR is release-shaped (bareVERSIONversus base) — a warning line there is cheap. Scope this to a warning; the reconciler must not guess intent by settingrelease(LABELS.md's rule forblocked/release).The scope job's
concurrency: cancel-in-progress: trueis not the bug and is not in scope — the cancelled job in that run wasreconcile, and the scope job ran to completion.Tasks
.github/workflows/labels.yml:44-46agree with the behavior.releaselabel, unless the fix makes it unreachable by construction.changelog.d/<this issue>.md.Acceptance criteria
labels / scopeis running keeps it — demonstrated on a real run, with the timeline in the PR body.scope:*labels are still applied, and a hand-applied scope still survives (the propertysync-labels: falsewas meant to buy).releaselabel draws exactly one warning per sweep, and the reconciler never setsreleaseitself.#128's incident is linked from wherever the fix is explained, so the next person finds the evidence and not just the patch.Test plan
scope:*that the globs do not derive — the label survives a scope run (the existing property, which a switch to additive writes must not break).releasewarns once; the same PR withreleaseis silent; an ordinary-devPR withoutreleaseis silent.Dependencies
Found on #118 (
0.2.0), which is unblocked and does not wait on this — its label is restored and its builder has been asked to re-check it before merge. Related machinery: #10 (the labels workflow), #18 (the issue-flow sweep).🔨 Claiming — starting now. Plan: reproduce the clobber on a scratch PR with a label written while
labels / scopeis in flight and capture the labeler's API call from the run log; then fix the write path per the spec (additivePOSTof derived scopes if labeler sets rather than adds), true up thelabels.ymlcomment, and add the reconciler warning for a release-shaped PR missingreleaseunless the fix is additive by construction. Draft PR soon.PR #133 is ready-for-review with the full panel requested (codex, grok, kimi) — this claim is now parked: the round is awaiting its first verdicts, and the next move belongs to the reviewers. Evidence summary on the PR: clobber reproduced on a scratch repo at ceremony's exact labeler pin (release removed by the labeler's PUT, same-second same-actor timeline as #128), fix demonstrated on a real run of the candidate workflow (mid-job label survives the additive POST), hand-applied scope survival and the release-shape warning both probed live. Scratch repo archived; deletion is the operator's.
Round 1 answered whole on PR #133 (head
4002924): both blocking points fixed (labeler.yml header truth, scope:labels mapping + tested coverage), grok's nits folded in, codex re-requested. The claim is parked, shape 2 again — the round was answered whole and the non-approver re-requested; the next move is codex's verdict.