actions/labels-reconcile — the shipped needs-triage description still routes filers to a discussion, so every governed board contradicts LABELS.md since #247
#265
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#265
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
LABELS.mdand the reconciler disagree about whatneeds-triagemeans, andthe reconciler is the half that reaches every board.
aa167fdon 2026-08-25T15:09:51Z —LABELS.md#L54: "a proposal or stray issue that did not come through triage — it owes normalization into work or a reasoned refusal".actions/labels-reconcile/labels-reconcile.sh#L718:needs-triage|FBCA04|Did not come through triage — owes normalization or conversion to a discussion.core_label_rows()is the taxonomy every governed repo receives, so thatsecond string is not a comment about a path — it is the path, printed on
the board next to the label. #247 removed the discussion door from this fleet
(this Forgejo exposes no Discussions;
.github/DISCUSSION_TEMPLATE/isdeleted; intake is a proposal issue stamped
needs-triage). The row wascorrect when
60e417awrote it on 2026-07-22 and stopped being correctthirty-four days later. #247's own acceptance criterion 6 required its diff over
actions lib bin .github/workflows .github/scripts .github/labels.confto beempty, so this line was out of that issue's reach by construction — it was
recorded as residue on #247 at 2026-08-25T13:46:36Z and is being minted here,
which is the only place it can land.
Measured on the live boards, 2026-08-25 — every governed board carries the
stale sentence today:
needs-triagedescriptionheavy-duty/ceremonyheavy-duty/crewheavy-duty/righeavy-duty/box(
box's is an older wording predating the centralized row; it has never beenre-bootstrapped. It is named here as blast radius, not as a deliverable.)
Why this is worth a fix and not a shrug.
needs-triageis the one labelwhose whole job is to tell a filer who has never read
TRIAGE.mdwhat happensnext. It currently tells them their issue may be converted into a discussion —
on a forge with no discussions, in a fleet whose intake door #247 just moved.
The description is the only doctrine most filers will ever read, and it is the
one that lies.
The same commit left a second, smaller echo:
labels-reconcile.sh#L726reads
and paraphrases a sentence #247 also rewrote:
LABELS.md#L245now reads "a
questionbelongs in a proposal, not a work issue". Those twolines are the only occurrences of "discussion" in
actions/labels-reconcile/labels-reconcile.sh, and the row at:718is theonly occurrence of the stale sentence anywhere in the tree — no test asserts
it, so nothing goes red on the edit.
Spec
:718gets doctrine's meaning in the row's own register. The new row is,byte for byte:
Name, colour and field count unchanged; the description carries no
|(
configured_label_rowsrefuses those, and the row parser splits on it).The wording is
LABELS.md#L54's second clause in the terse, capitalizedform every other core row already uses.
Do not try to make the row byte-identical to
LABELS.md's cell. Thatbinding is tempting —
retired_label_names()already earns it, andtest/labels-reconcile.test.shparses the six names out ofLABELS.md'sown parenthetical and demands identity. It does not generalize to the
descriptions, and this was measured rather than assumed: of the twenty core
rows,
readymatches itsLABELS.mdcell modulo the leading capital whilestate:building,state:addressing,needs-rulingandattentionaredeliberately different registers — the table's "Means" column answers
whose ball, the row is a one-line tooltip. A table-wide identity guard
would be a rewrite of nineteen rows this issue has no mandate for. One row,
one guard.
:726's comment is corrected, and gets no guard. Make it read that thedefaults
LABELS.mdretires are retired because aquestionbelongs in aproposal, not a work issue — matching
LABELS.md#L245. It is a comment on afunction whose contents are already held against doctrine by an existing
test; adding a second test to hold a comment is noise, and this issue says so
rather than leaving a reviewer to ask.
A guard, so the row cannot rot silently again. Two cases in
test/labels.test.sh, beside theattention_rowprecedent attest/labels.test.sh#L29-L33:needs-triagerow is emitted once, byte-exact, in the shape specitem 1 fixes (
core_label_rows | grep -cxF "$needs_triage_row"is1);core_label_rowsoutput names no "discussion", case-insensitively.The second case must fail on
origin/mainas it stands and pass afterthe edit. Write it in that order and record both runs in the PR.
No other file changes.
LABELS.mdstays byte-identical — it is alreadycorrect, it is mirrored into every consumer's
.ceremony/, and a PR thatedits it has misread this issue. Nothing under
lib/,bin/,.github/workflows/or.github/labels.confis touched. The out-of-fleet"discussion" mentions stay:
.github/scripts/release-path.sh:2(a citation of discussion #217, a historical artifact), and the
RELEASES.md/drills/0.2.0.md/docs/CONSUMERS.mdmentions #247examined by name and deliberately kept.
Fragment. One
changelog.d/265.mdin the grouped shape theshapesentinel declares, under
### Fixed.The live boards are refreshed by a bootstrap dispatch, not by this merge.
bootstrap_labelsis dispatch-only —.github/workflows/labels-sweep.ymldeclares
bootstrapdefaulting to"no", and only a manualworkflow_dispatchof the sweep caller (self-labels-sweep.ymlhere)carries
bootstrap=yes; cron- and event-woken sweeps never re-upsert thetaxonomy.
forge_label_createis a true upsert — it PATCHes the descriptionof an existing label — so one bootstrap dispatch per board is all it takes,
and none is the builder's to run. Criterion 8 carries this, post-merge.
Tasks
origin/main.test/labels.test.sh; run themagainst the unedited reconciler and record the "no discussion" failure.
labels-reconcile.sh:718to the byte-exact row in spec item 1.retired_label_names()comment at:726.test/labels.test.shgreen, thenbash test/run.shwhole.changelog.d/265.md.git diff --check.git diff origin/main..HEAD --statnames exactlyactions/labels-reconcile/labels-reconcile.sh,test/labels.test.shandchangelog.d/265.md.Acceptance criteria
core_label_rowsemitsneeds-triage|FBCA04|Did not come through triage — owes normalization into work or a reasoned refusalexactly once, byte-exact, and emits no otherneeds-triagerow.git grep -i discussion -- actions/labels-reconcile/labels-reconcile.shreturns nothing at the PR head.
retired_label_names()comment names the proposal path, matchingLABELS.md#L245; the six names it returns are unchanged andtest/labels-reconcile.test.sh's LABELS.md-identity case is still green.test/labels.test.shcarries a case that fails on the pre-changereconciler and passes after, and the PR records both runs verbatim.
git diff origin/main..HEAD --statnames exactly three files:actions/labels-reconcile/labels-reconcile.sh,test/labels.test.sh,changelog.d/265.md. In particularLABELS.mdis byte-identical.bash test/run.shis green whole at the PR head;git diff --checkisclean; the shellcheck sweep is clean.
changelog.d/265.mdexists, carries a### Fixedheading per thegrouped
shapesentinel, and names #265.heavy-duty/ceremony'slive
needs-triagelabel description equals the string in criterion 1.Mechanism: the merge alone does not change it — a
workflow_dispatchof
self-labels-sweep.ymlwithbootstrap=yes(its default) must runafter the merge, and that dispatch is the operator's, not the builder's
and not triage's. Wake condition: the first such dispatch after the
merge; triage reads the live label back through the labels API and
closes. If no bootstrap dispatch has landed within seven days of the
merge, triage asks @andres for one in a comment here rather than letting
the issue sit silent. Because this criterion outlives the merge, the PR
references this issue with
Refs #265and carries no closingkeyword; the merge moves this issue to
post-mergeand releases theclaim.
The three sibling boards (
crew,rig,box) are out of scope: each isgoverned by its own triage and refreshes on its own bootstrap dispatch off the
same fixed row. No cross-repo issue is owed — the fix ships here, once.
Test plan
bash test/labels.test.shwith the new"no discussion in
core_label_rows" case, run against the uneditedlabels-reconcile.sh: the case reports the:718row. Record the output.swap "into work" for "to work") and confirm the byte-exact case goes red —
a
grep -cxFguard that passes on a paraphrase is not a guard. Revert.bash test/labels.test.sh, thenbash test/run.shwhole — 31 test files, 0 failed, exit 0 (the count measured at
aa167fd;say so if it moves).
|; confirm therow still splits into exactly three fields by the same
while IFS="|" read -r name color descshapetest/labels.test.shalreadyuses for
attention.bootstrap_labelsstill upserts the same name set.test/labels-reconcile.test.sh'sexpected_upsertscase is derived fromcore_label_rows | cut -d'|' -f1, so a description-only edit must leave itgreen; a red there means the row's field count changed.
Dependencies
None, in either direction, and both halves were measured at mint rather than
assumed:
zero open pull requests at 2026-08-25 (
issues?state=open&type=issues→ 0,
type=pulls→ 0; the repo's ownopen_issues_countagrees). There isno open
ready,claimedorblockedcarrier of any file, so no collisionedge exists to declare.
release; ceremony's 0.6.2 windowclosed with #231/#228 on 2026-08-25 and
VERSIONreads0.6.3-devwith nosuccessor release issue open. No membership call is owed.
Starting #265 as codex-bot-andresmgsl.
Plan of record:
The post-merge bootstrap dispatch remains operator-owned and is not part of this build.
Moved to
post-merge; claim released. !266 merged asd439ff6at2026-08-25T22:20:01Z with
Refs #265, exactly as criterion 8 requires. The moveand the unassignment are triage's, by hand, in the same tick as this comment —
the sweep's derived transition fires from
pull_request_targettypes that do notinclude a merge, so it would have waited a cron cadence.
Criteria 1–7: verified and ticked, measured at
d439ff6core_label_rows | grep -cxF "needs-triage|FBCA04|Did not come through triage — owes normalization into work or a reasoned refusal"→1;grep -c '^needs-triage|'→1. No otherneeds-triagerow.git grep -i discussion -- actions/labels-reconcile/labels-reconcile.sh→ no output, exit 1.:726now reads "the GitHub defaults LABELS.md retires — aquestionbelongs in a proposal, not a work issue", matchingLABELS.md#L245("aquestionbelongs in a proposal, not a work issue"). The six returned names are unchanged, andtest/labels-reconcile.test.sh's LABELS.md-identity case is green in the whole-suite run below.0ea0cf5(tests added, reconciler untouched) runsbash test/labels.test.sh→44 passed, 2 failed, failing both the byte-exact row case and the no-discussion case. The same file atd439ff6→46 passed, 0 failed. Both runs are also recorded verbatim in !266.git diff --stat d439ff6^1..d439ff6^2names exactlyactions/labels-reconcile/labels-reconcile.sh,test/labels.test.sh,changelog.d/265.md.git diff d439ff6^1..d439ff6^2 -- LABELS.mdis empty: byte-identical, as spec item 5 demands.bash test/run.shatd439ff6→test files: 31 passed, 0 failed, exit 0 — the count the test plan pinned ataa167fd, unmoved.git diff --checkclean.bash .github/scripts/shellcheck-all.shexit 0 over the tracked scripts. CI at the PR head53b7856is green on every context (test,release-exercise,self-guards,action-exercise,docs-sync-exercise,labels,refs-not-closing), latest runs17198–17214.changelog.d/265.mdexists, carries### Fixedper the groupedshapesentinel, and names #265.Criterion 8 remains open — owner and wake condition
Measured on this board at 2026-08-25T22:23Z, the live label still reads:
That is expected and is not a defect in the merge.
bootstrap_labelsisdispatch-only:
labels-sweep.ymldeclaresbootstrapdefaulting to"no", andonly a manual
workflow_dispatchofself-labels-sweep.ymlcarriesbootstrap=yes. Cron- and event-woken sweeps never re-upsert the taxonomy, sono amount of waiting moves this by itself.
workflow_dispatchofself-labels-sweep.ymlwithbootstrap=yesafterd439ff6. Triage reads the description back through the labels API, ticks criterion 8 against the string in criterion 1, and closes.forge_label_createPATCHes an existing label, so one dispatch is all it takes.Nothing is owed by any builder.
crew,rigandboxstay out of scope: eachrefreshes on its own board's bootstrap dispatch off this same fixed row.
Criterion 8 verified; closing. The bootstrap dispatch criterion 8 waited on
landed today, and the live board now carries the corrected row.
The wake
self-labels-sweep.ymlrun #2586(
id=25752,event=workflow_dispatch,head_sha=58ab503) ran2026-08-30T15:53:26Z → 15:54:07Z, success. It is the first bootstrap dispatch
of this sweep since !266 merged as
d439ff6on 2026-08-25T22:20:01Z.It is a real bootstrap, not a trigger-job dispatch. Two independent proofs:
Before/after across the run. I read the live label at 15:53:0xZ, while
#2586 was still
running, and again after it reported success:The whole taxonomy was re-upserted.
comm-ing all twenty rows ofcore_label_rowsat58ab503(lowercased name+colour, descriptionverbatim) against the live label set returns an empty
expected-but-missing-live side — every core row is present byte-exact. A
bootstrap=nosweep never re-upserts the taxonomy, so this could not havebeen the trigger job's dispatch. (The live-only side is just this repo's own
configured labels:
bug,documentation,enhancement,blocker:drill-pending, and the fourscope:*rows. Expected, not core.)Every other
workflow_dispatchof the sweep in today's run history fires1–2 seconds behind a
self-labels.ymlorrefs-guard.ymlevent — thetrigger-job shape. #2586 stands alone with no paired event.
Criterion 8, measured
heavy-duty/ceremony's liveneeds-triagedescription, read back throughGET /repos/heavy-duty/ceremony/labels, is byte-identical to criterion 1'sstring —
cmpagainst a locally written copy of that string reports nodifference, and the description ends
...r a reasoned refusalwith no trailingwhitespace. Exactly one
needs-triagelabel exists on the board.(The colour arrives lowercased by the API; criterion 8 binds the description,
and the row's
FBCA04is unchanged.)Criteria 1–7 were verified and ticked at
d439ff6in the transition commentabove. With 8 now ticked, no acceptance criterion remains unchecked and no
builder is owed anything. The seven-day fallback ask to @andres, which would
have come due 2026-09-01T22:20Z, is spent unused.
crew,rigandboxremain out of scope by this issue's own terms: eachrefreshes on its own board's bootstrap dispatch off this same fixed row, and no
cross-repo issue is owed.
Closing. The
post-mergelabel stays as the record of how this issuecompleted; the queue-state invariant governs open issues only.