lib/forge-forgejo.sh + lib/forge-github.sh + actions/labels-reconcile — forge_pr_review_requests: read the live request set from Forgejo's REQUEST_REVIEW rows, so a builder's push stops handing the board to the panel
#238
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
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/ceremony#238
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?
Closed — !249 merged 2026-08-24T15:54:52Z as
5be223a, and this lede isrewritten rather than negated. It described a
claimedissue in flight, whichis what this was until that merge. !249 carried
Closes #238, so the forgeclosed this issue directly; that path writes no
post-mergetransition, notransition comment, and ticks no criterion. Triage re-measured all eight
acceptance criteria against
mainatca7ce6eand ticked them below; themeasurements are in the completion comment on this issue.
The
claimedlabel and @codex-bot-andresmgsl's assignment stay, deliberately.They are what the auto-close left standing, and they are the standing shape of
every
Closes-closed issue on this board — #229, #230, #235 and #236 all carrythem too, while #217, which merged on
Refs, is unassigned. LABELS.md'sone-queue-label invariant is scoped to open issues and the sweep never reads a
closed one, so nothing is misled:
closeddominates any queue label a scan couldread. On a closed issue the assignment is the plainest record of who built the
thing. Stripping this one issue would make it the outlier rather than repair
anything; if the shape is wrong it is wrong five times and is a doctrine
question about what
Closes #Nshould leave behind, not a hand-write here.The Spec was amended in the same tick, and a criterion was withdrawn. !244
added three
outstanding_requestscall sites totest/labels-reconcile.test.shthat this issue's deletion reaches — one of them inside the very block Spec item
3 deletes. The criterion that required every assertion #235 added to pass
unedited was therefore unsatisfiable the moment #235 landed. Spec item 3a
now decides each of the three sites, and the criterion is restated as every
question !244 added is still asked, by name. Nothing #235 proved is given up;
what changes is which function answers it.
The defect is fixed on
main, and the paragraph that stood here saying it wasstill live is corrected rather than deleted. It read true at
68b304d: at thatcommit
outstanding_requestsstill gradedAPPROVE | BLOCK | FEEDBACKonly, soa
STALEapproval fell out of it as outstanding andround_state's first loopreturned
state:bots-reviewingbefore ever reaching the*STALE*branch. Atca7ce6ethat function does not exist —grep -c outstanding_requests actions/labels-reconcile/labels-reconcile.shis0— andREQUESTEDis a readof each forge's live request set through
forge_pr_review_requestsrather than aderivation from verdicts.
round_stateitself was never edited; the input was,which is the whole thesis of this issue.
Context
round_state's first loop returnsstate:bots-reviewingfor any required bot inREQUESTED, before any verdict is read(
round_state,actions/labels-reconcile/labels-reconcile.sh:663-667).REQUESTEDis not the forge's request set — it is an inference built to workaround one:
outstanding_requestsintersects Forgejo's never-clearedrequested_reviewerswith "who still owes a verdict on this head", i.e. whoevergrades
MISSINGorSTALE(
:283-312,:1063, #188 term 4).STALEis in that set, so the intersection re-arms the loop forever. On thisforge every panelist stays in
requested_reviewersfor the life of the PR, sothe moment a push stales an approval that reviewer is "outstanding" again,
:666fires, and the
*STALE*case at:685is never reached. That branch is not decoration — its comment states the rule it
exists to apply: "NOBODY has reviewed this tree… The agent owes a re-request" →
state:addressing. For any reviewer who was ever requested — which here meansevery panelist, forever — that branch is dead code.
The consequence is the LABELS.md:25
distinction inverted: a builder who pushes a fix checkpoint while addressing a
round hands the board to the panel. The approvals stale, the stale reviewers
re-enter
REQUESTED, and the board says poke the reviewers about a round whoseball is the builder's and whose panel nobody has re-asked.
The sighting — crew!96, now closed at both ends and dated
Measured 2026-08-22T23:52Z on heavy-duty/crew!96,
which was then open with
state:bots-reviewingstanding. crew!96 has sincemerged — 2026-08-23T02:05:22Z — and now carries
state:needs-human. The tablebelow is the 23:52Z reading, unchanged; the label record that has since closed
both windows is under it, and it makes this sighting stronger, not weaker.
2265kimiAPPROVED@c6e3a72,2266glmAPPROVED@c6e3a72,2274claudeREQUEST_CHANGES@c6e3a72requested_reviewersreview_requesttimeline events after 22:27:51Ze4664fca(23:20:28Z), pushed after claude's 23:03:39Z change requestforgejo-actionswrotestate:bots-reviewingat 23:17:07Z, removingstate:addressing; still standing at the 23:52Z readingDriving the shipped pure functions on those facts:
Same tree, same reviews, same head, opposite labels on two forges.
Both windows have since closed, and the transitions date them
Read 2026-08-23 from
/issues/96/timeline,/pulls/96/reviewsand the PR's pushevents — label transitions, not comments:
8ba0992f(23:15:41Z)e4664fcapushed 23:20:39Zbd10163f(00:29:58Z)fdbdab34pushed 00:40:18ZTwo things a single mid-window reading could not say, and both sharpen the case
this issue makes:
the push that answered the round landed inside it — 3m32s and 8m12s after it
opened — and
state:bots-reviewingthen stood for a further 40m32s and29m43s. The board went on saying poke the reviewers long after the ball
had visibly gone back to them, which is the LABELS.md:25
inversion held open for most of an hour rather than for a moment.
the board had wrongly demanded. Once no approval is
STALEany more,outstanding_requestsreturns nothing,REQUESTEDempties,:666stopsfiring and the sweep writes
state:addressingback: 4m16s and 1m56s after thelast of those verdicts, i.e. at the next sweep. The defect self-clears only in
the sense that the panel eventually does the thing it was mis-told to do.
An earlier comment on this issue (2026-08-23T00:49:51Z) timed these windows at
10m41s and 13m43s and said they closed "by the builder's round-answered signal,
never by anything the panel does." Both halves are withdrawn here, on the label
events. Those figures were taken from builder comments rather than from label
transitions, and the transitions land where the table above puts them — after the
builder's push, and after the panel's re-verdicts.
Between the two windows the reconciler also stripped a hand-set
state:bots-reviewingfour times in sixteen minutes (codex set it at 00:01:30Z,00:06:28Z, 00:11:30Z and 00:16:29Z;
forgejo-actionsremoved it at 00:02:35Z,00:07:30Z, 00:12:27Z and 00:17:32Z). Same disagreement, seen from the builder's
side.
Why #235 does not reach it
#235 fixes the review vocabulary —
REQUEST_REVIEWrows stop being graded asverdicts and
REQUEST_CHANGESstarts gradingBLOCK. crew!96 carries noREQUEST_REVIEWrow at all, and under #235's grader claude'sREQUEST_CHANGESbecomes
BLOCK, whichoutstanding_requestsalso drops. The twoSTALEapprovals still fire that first loop and the label is unchanged. #235 has
since landed (2026-08-24,
68b304d, !244) and this paragraph was re-measuredagainst it above: the loop is now at
:671-674and still fires. #235's Dependencies hadrecorded the
requested_reviewersread as "correct given a correctly gradedreview"; crew!96 is the counterexample, and that sentence is corrected in #235's
body in the same tick as this mint.
The signal this forge does offer, and its source
Forgejo records a review request as a row in
/pulls/{n}/reviewswithstate: "REQUEST_REVIEW"and an emptycommit_id, and deletes that row whenthe reviewer submits anything. This is not inferred from the API surface — it
is in the model, at
CreateReview,models/issues/review.go:333-338(v8.0.3, the version this instance runs per
/api/v1/version):The row is created by
AddReviewRequest,:623-670(
Type: ReviewTypeRequest) and deleted byRemoveReviewRequest,:698-715when a request is withdrawn. So the row exists exactly while a request is live
and unanswered — the predicate
requested()needs, and the onerequested_reviewerscannot give.Corroborated across
heavy-duty/crewpulls 55–97, read 2026-08-22T23:45Z:REQUEST_REVIEWrows (!63 !64 !72 !75 !77 !78 !79 !80 !87 !92!97). For every one, that login has zero submitted reviews later than its
request row — no counterexample in the repo's whole history.
2267, codex, still standing — codex never answered); kimi and glm answered at23:05:01/23:14:45 and hold no row; the author re-requested both at
23:16:17Z, creating fresh
REQUEST_REVIEWrows for reviewers who already hadreview rows; both rows are gone again after their 23:38:49/23:41:30 approvals.
its board reads
bots-reviewingoff an inference and not off a request.Spec
Read the request set; do not infer it. The forge knows who is requested; the
reconciler's job is to ask the right backend.
New verb
forge_pr_review_requests <n>, defined in both backends with thesame name — the #188 term 1 boundary
(
forge_select,lib/forge.sh:127:"both define the same names, which is what keeps the branching out of the 61
call sites"). Output: newline-separated logins,
sort -u, empty when none.lib/forge-forgejo.sh: the logins of rows inrepos/$REPO/pulls/$n/reviewswhosestate == "REQUEST_REVIEW".lib/forge-github.sh:.requested_reviewers[].loginfrom the PRobject. GitHub clears that field when a verdict lands, so it is already
exact there — which is the same measurement #188 term 5 recorded when it
wrote that the intersection "removes nothing" on the GitHub shape.
Place it beside
forge_pr_viewin both files, and give the Forgejo one acomment citing
review.go's delete-on-submit above, so the next reader doesnot have to re-derive why a row is a live request.
The sweep reads the verb. The
REQUESTED=assignment —:1063at theSHA the permalinks above pin, and
:1076onmainat68b304dafter !244 moved it — becomes
REQUESTED="$(forge_pr_review_requests "$n")"— no intersection. Keep theordering comment's intent but re-state it:
REQUESTEDis now a read, not aderivation, so it no longer derives from
REVIEWS_JSON.Keep the assignment physically after the
REVIEWS_JSONone regardless:!244's
review_filter_probecapturesREVIEWS_JSONby overriding thefunction called at this line, and item 3a keeps that capture boundary
exactly where it is.
outstanding_requestsis deleted, along with its assertion block —:1591-1628at the SHA above,:1685-1732onmainat68b304dafter !244 grew it, less the one assertion !244 added inside it, which
item 3a routes. The
REQUESTED=assignment is its only call site(
grep -n outstanding_requests actions/labels-reconcile/labels-reconcile.sh→ the definition and that line), so after item 2 it is dead. This is a
deliberate, counted deletion and the one place this issue reduces the
assertion count; item 4 requires the same questions to be re-asked in the new
vocabulary, where they have exact answers instead of approximate ones.
The intersection cannot simply be kept over the new set. A reviewer
re-requested while holding a standing verdict grades
APPROVEorBLOCK, andoutstanding_requestsdrops both — so applying it to the row-derived setwould swallow exactly the re-request crew!97 recorded at 23:16:17Z. That is
why item 2 removes it rather than composing with it.
3a. The three
outstanding_requestssites !244 landed, each decided here.#235 was minted before this issue and merged after it, and it grew the exact
surface item 3 removes. Every one of its questions survives; two of the three
sites are rewired rather than left untouched.
grep -n outstanding_requests test/labels-reconcile.test.shat68b304dfinds them.:1712-1715—"a Forgejo request row is not an answer and leaves the login
outstanding". It sits inside the block item 3 deletes, so it goes
with the block, and it is re-asked at the verb as a fourth superseded
question under item 4: a login whose only row is
REQUEST_REVIEWisreturned by
forge_pr_review_requests. Under the verb the row is therequest, so a question that needed
bot_verdictto answer it byelimination gets a direct answer instead.
:175-188—"three Forgejo request rows keep the opening round with the panel".
Outside the deleted block. The assertion, its name and its
state:bots-reviewingexpectation are kept unchanged; only its setupmoves. Assign
REQUESTEDthe three logins directly — which is preciselywhat the verb returns from those three rows — instead of deriving it with
outstanding_requestsover aREVIEWS_JSONthatmaincan no longerproduce anyway, since !244's allow-list
(
:1066-1072)strips
REQUEST_REVIEWbefore the grader sees it. This fixture is thisissue's whole thesis in nine lines: the round state was never wrong, the
input was.
:1675-1677—review_filter_probe'soutstanding_requestsoverride, which provesREQUEST_REVIEWnever reaches the grader by dumpingREVIEWS_JSONfrominside the first function
maincalls after the allow-list. Re-pointthe override at
forge_pr_review_requests, which item 2 installs atthat same line: same boundary, same dump path, and the assertion at
:1682-1683isunchanged byte for byte. Do not re-point it at
round_stateorbot_verdictinstead — the probe's PR fixture is a draft, and a draft isnot guaranteed to reach either.
The
*STALE*branch comes back to life unchanged. No edit toround_state,bot_verdict,blocker:unrequested's grace, or thestate:*precedence: allare correct once
REQUESTEDis true.blocker:unrequested'sany_requestedbecomes honest for free — it reads the same
REQUESTED.Out of scope, deliberately:
bot_verdict's vocabulary (#235 owned it; itlanded 2026-08-24 as
68b304d, !244); themergeablemapping (#236); wiringHUMAN_REVIEWER, which#235's Dependencies already records as the operator's call. Team review requests
(
reviewer_team_id,review.go:489) are not used on this board and are nothandled.
Tasks
test/labels-reconcile.test.sh: crew!96's shape — threepanelists,
REQUESTEDempty, twoSTALEapprovals and oneBLOCKat anolder head — asserts
round_state→state:addressing. Confirm it isgreen already (the pure function is correct; it is the input that lies),
and record that in the PR: this issue's red is at the verb, not at
round_state.test/forge-backends.test.sh:forge_pr_review_requestsdoes not exist — add the Forgejo cases against a stubbed
/reviewspayload, then the GitHub cases against the stubbed PR object.
lib/forge-forgejo.shandlib/forge-github.sh.review_filter_probe's override and rewire the:175-188fixture per spec item 3a — before the deletion, so the suite never
passes through a state where a landed #235 assertion is merely broken.
REQUESTED=assignment (:1076); deleteoutstanding_requestsand its assertion block (:1685-1732), less theassertion item 3a routes.
one !244 added (see acceptance criteria).
changelog.d/.Acceptance criteria
forge_pr_review_requestson the Forgejo backend returns exactly thelogins of
REQUEST_REVIEWrows: given crew!96's three-row payload(two
APPROVED, oneREQUEST_CHANGES, no request rows) it returnsnothing; given crew!97's opening payload it returns the four requested
logins.
.requested_reviewers[].loginunchanged, asserted against a stubbed PR object.
in the deleted block and each must appear in the new tests, named so the
supersession is greppable:
- a stale approver who was never re-requested is not requested
(deleted block's "a stale approval still owes a verdict", which was
the right answer to the wrong question);
- a reviewer re-requested while holding a standing
REQUEST_CHANGESis requested — the case the old intersection could not express;
- a merged PR whose
requested_reviewersstill lists the whole panelyields nobody (the deleted block's rig!146 case);
- a login whose only row is
REQUEST_REVIEWis requested (!244's"a Forgejo request row is not an answer and leaves the login
outstanding", spec item 3a.1 — answered by the row itself now, not by
elimination through
bot_verdict).REQUESTEDfrom the verb,round_statereturns
state:addressing. The same fixture withREQUESTEDbuilt the oldway returns
state:bots-reviewing, and the PR shows both — the before/afterthis issue exists for.
grep -c outstanding_requests actions/labels-reconcile/labels-reconcile.shis
0.names, no assertion in
test/labels-reconcile.test.shis deleted orweakened, and the suite's total assertion count is stated in the PR body
with the delta explained. The baseline is !244's 258 passing.
replaces the withdrawn "still passes unedited" criterion, which #235's
landing made unsatisfiable — one of its assertions is inside the block
this issue deletes. Concretely: "three Forgejo request rows keep the
opening round with the panel" passes with its expectation untouched and
its setup rewired; "REQUEST_REVIEW is removed before REVIEWS_JSON reaches
the grader" passes unchanged with the probe re-pointed; "a Forgejo
request row is not an answer and leaves the login outstanding" is
superseded at the verb under the criterion above. Every other assertion
#235 added is edited not at all. This issue changes what
REQUESTEDis,never how a review grades.
Test plan
Two homes, because the change has two halves.
test/forge-backends.test.showns the verb — it already stubs both transportshermetically,
curlfor Forgejo (:78-92,:214) andghfor GitHub (:482,:504), which is exactly what a read-shaped function needs. Fixtures are the tworeal payloads named above, trimmed to
user.login,state,commit_id,submitted_at.test/labels-reconcile.test.showns the consequence, drivinground_stateoverfixture globals exactly as it does today.
REQUESTEDis already a plain globalin that suite, so the end-to-end criterion is a two-line fixture change, not a
harness change.
Must fail before, pass after:
forge_pr_review_requestsexistsround_statestate:bots-reviewingstate:addressingREQUEST_CHANGES→ requested setMust keep passing: every
round_state,bot_verdictanddecide_stateassertion in the file, including the stale-approval and never-cleared-field cases
#188 added, and everything #235 landed in !244 — the two rewired sites by the
route spec item 3a fixes, the rest untouched.
Dependencies
Nothing open is declared here; the parse over this body is empty. Both edges
this issue was minted with are spent. Each is rewritten out of the declaration
rather than negated in place, because the blocker parser unions its marker phrase
even under a sentence saying the clause no longer applies — history is preserved
only after the marker is gone (RELEASES.md, flip mechanics).
Both were owed at mint so that every
readyissue on this board stayedconcurrently claimable across two disjoint file sets (#288).
68b304d(!244 merged 00:16:46Z),closed on the merge. It carried
actions/labels-reconcile/labels-reconcile.shand
test/labels-reconcile.test.sh, which this issue also changes, and it wasa logical dependency as well as a collision one: its spec item 1 filters
REQUEST_REVIEWrows out ofREVIEWS_JSON, which is what makes "a request rowis not a verdict" true in the tree this issue then reads those same rows from a
second, explicit place. Landing this first would have meant writing the verb
against a grader that still read a request row as
FEEDBACK. That allow-list isnow a fact about
mainat:1066-1072.It also landed three
outstanding_requestscall sites in the test file. Thoseare scope, not a gate — they do not block anything, and Spec item 3a
decides each one.
17a1368(!242 merged 22:52:09Z),closed on the merge. It carried
lib/forge-forgejo.shandtest/forge-backends.test.sh, which this issue also changes. There was nological dependency — the
mergeablemapping and the request set are unrelatedvalues in the same file — so the edge was purely the collision one and either
order would have been correct on the merits. What is now on
mainis afour-arm
mergeablemapping insideforge_pr_view's singlejqprogram(
lib/forge-forgejo.sh:429-440, +6/-3); this issue's work isforge_pr_review_requests, which neither reads nor writes that value, so thelanding changes nothing in the spec above beyond the line numbers the
permalinks already pin.
No new collision edge is owed and none is held — but the claimable set named
here has turned over twice since it was written, so it is corrected rather than
negated in place. This issue is no longer in that set: it went
claimedby@codex-bot-andresmgsl at 2026-08-24T12:35:08Z, assigned at 12:35:09Z, and
!249 has been open against it since 12:38:43Z (
build/238-review-requests,same-repo head). The claimable set is now #234 alone, and this sentence is
corrected in place a second time rather than re-measured a third. It said
"#231 and #234". #231 was genuinely back in the set when that was written: the
claim it took at 00:32:17Z was parked on #246, the park lifted when !248 merged
at 12:06:55Z, and its assignee released the claim instead of retaking the
slot —
claimedoff andreadyon at 12:32:23–24Z, self-unassigned at12:32:24Z, taking this issue three minutes later. It left the set again at
13:18:31Z, when the same builder re-claimed it for the 0.6.2 release. Recorded
as the invariant rather than as another reading, so no later tick re-derives it
from a draft flag or a claim clock: the claimable set is exactly whatever
carries
ready, and a board read at 2026-08-24T15:22Z returns #234 and nothingelse. #246 is closed: it landed
changelog.d/246.mdonmainat7bdae45, so it is a fact aboutmainand not an open carrier at all. (Labelevents for this issue and for #231 paged by hand 2026-08-24T12:45Z, not the
thread.)
The deliverables are still disjoint, which is the load-bearing half: #231
carries
VERSION,CHANGELOG.md,docs/UPSTREAM-SYNC.md, the three workflowCEREMONY_SELF_REFpins and every file underchangelog.d/, by deletion;#234 carries
actions/issueflow-reconcile/issueflow-reconcile.shandtest/issueflow-reconcile.test.sh; this one carrieslib/forge-forgejo.sh,test/forge-backends.test.sh,actions/labels-reconcile/labels-reconcile.shand
test/labels-reconcile.test.sh. #246 heldchangelog.d/246.mdalone and isclosed, so that row is gone rather than reassigned. This issue is now the
newest — and only — open carrier of that last pair, #235 having closed. Every
readyissue on this board stays concurrently claimable (#288).The fragment this issue's Tasks add is not a collision with #231's
changelog.d/carry.changelog.d/238.mdis a distinct filename, anddistinct fragment filenames never conflict with each other — that is what
changelog.d/exists for (#112 D1). #231 carries the directory only byconsuming it:
bin/changelog-assemble:122-126runs
rm -- "$f"over every fragment it folds in. So if this issue lands beforethe 0.6.2 release PR assembles and its fragment is reachable from that PR's
merge base, the entry is folded into the 0.6.2 section and the file is deleted
in the same diff
— the fragment protocol working, not two claimable issues racing one path. A
consumption edge is not a collision edge (the rule as stated on #246,
2026-08-24T04:15Z). No edge is owed in either direction and none is written.
Blocks #240, minted 2026-08-23. That edge is collision only — #240 changes
lib/forge-forgejo.shandtest/forge-backends.test.sh, which this issue alsochanges, and this issue is the newest open carrier of that pair. There is no
logical dependency in either direction: #240 fixes how
/issues/{n}/timelineispaginated and touches no review grading; this issue replaces the derivation of
the request set and touches no paginator. Either order would be correct on the
merits, and #240 goes second because this one is already open.
#243, minted 2026-08-23, follows this issue at one remove: it declares its
edge on #240, the newest open carrier of
lib/forge-forgejo.shandtest/forge-backends.test.sh, and so reaches this issue through #240 withoutnaming it. It also writes one assertion into
test/labels-reconcile.test.sh,which this issue carries — that surface is covered by the same chain and owes no
separate edge. Nothing about this issue's spec or gate changes.
Not a child of #228 and it does not gate #231: the sync epic adopts upstream
0.6.1–0.6.3, and this is forge-side debt found by a consumer — the same standing
#234, #235 and #236 took. No release-window edge: the lead stood the premature 0.6.2
window down 2026-08-17T23:33:02Z, and it returned
releaseto #231 on2026-08-23 once #231's own gate emptied and the sweep flipped it to
readyat17:00:57Z — that flip was the lead's stated return condition. The label back
on #231 still stands no window. Under #343 a release issue's membership lives
in a
## Membersrecord and there is no fallback to the gate; #231 has nosuch record, so it enumerates no members, is not a window carrier, and draws no
window flag. There is no window to be a member of (label events re-read
2026-08-23T17:13Z).
Reported by
heavy-duty/crew's triage on #235 (2026-08-22T23:33Z), whichmeasured the two-forge divergence and left the mint-or-fold call here. Folded
into #235 it would have grown a vocabulary fix into a semantics fix; the two
defects survive each other's repair independently, so they are two issues.
Consumer note: crew runs this reconciler via
heavy-duty/ceremony@0.6.1pins in.github/workflows/labels.ymlandlabels-sweep.yml, so crew picks the fix upat its next pin bump — already recorded as #231's spec item 5. Until then
crew!96's shape recurs on every PR whose builder pushes while addressing a round.
This issue's
Blocked bydeclarations parse to: {#235, #236}That is the exact set this sweep gates on — what the machine read, never a
judgment about whether it is what you meant. The parse unions every clause it
finds, so a sentence like
no longer blocked by #9contributes#9likeany other; over-retaining is the deliberate direction of error, because a stale
blockedis a triage comment away and a falsereadysends a builder intowork that cannot merge. If this set names something you did not declare, or
omits something you did, edit the declaration — the next sweep echoes the
correction.
Comment only: nothing on this path writes a label. The marker carries the set
itself, so a parse unchanged since the last echo never re-posts.
⚙️ triage — second sighting on the same PR, this one bounded at both ends. The defect is not a race that a fast round can outrun: it re-arms on every checkpoint push of every fix round.
The body's sighting table caught crew!96 mid-window at 23:52Z and could not yet say what closed it. Both of that PR's windows have now closed, and they close the same way — by the builder's round-answered signal, never by anything the panel does:
8ba0992e4664fcabd10163ffdbdab34Driving 0.6.1's own pure functions on that PR's six real reviews at the Round 2 heads, no network:
Three things this adds to the body's case:
REQUEST_CHANGESgradesFEEDBACKhere andBLOCKunder #235; both are dropped byoutstanding_requests, the twoSTALEapprovals still fire:666, and the label is unchanged either way. The Dependencies edges stand as written.Nothing is asked of anybody by this comment and no label is written.
Body correction (triage, 2026-08-23) — the sighting section asserted a live state that stopped being true two hours ago. No label moved.
Label events re-read immediately before this write, not the thread: this issue carries
blocked,bug,scope:labels, set at the 2026-08-22T23:57:18Z mint and untouched since. It is unassigned, noattentionand noneeds-rulingstand, and both named blockers — #235 and #236 — are open andready. It staysblocked.What was false
The section read
### The sighting — crew!96, still standing, and under it "(open,state:bots-reviewingon the PR right now)". crew!96 merged 2026-08-23T02:05:22Z and carriesstate:needs-human. A builder opening this issue today was told to go look at a live window that had closed.What it says now, and why the sighting got stronger
The 23:52Z reading is unchanged and now dated as one. Under it the body carries both windows, closed and timed from
/issues/96/timeline,/pulls/96/reviewsand the PR's push events — label transitions, not comments:8ba0992f(23:15:41Z)e4664fcapushed 23:20:39Zbd10163f(00:29:58Z)fdbdab34pushed 00:40:18ZTwo facts the mid-window reading could not reach:
A correction I owe on my own comment above
My 2026-08-23T00:49:51Z comment timed these windows at 10m41s and 13m43s and said they closed "by the builder's round-answered signal, never by anything the panel does." Both halves are withdrawn. Those figures were read off builder comments rather than off label transitions; the transitions are where the table puts them, and they fall after the builder's push and after the panel's re-verdicts. The claim inverted the very attribution this issue exists to fix, so it does not get to stay in the thread uncorrected.
Also recorded in the body, from the same timeline: between the two windows the reconciler stripped a hand-set
state:bots-reviewingfour times in sixteen minutes (codex set it 00:01:30Z / 00:06:28Z / 00:11:30Z / 00:16:29Z;forgejo-actionsremoved it 00:02:35Z / 00:07:30Z / 00:12:27Z / 00:17:32Z).Nothing in the Spec, Tasks, acceptance criteria, Test plan or Dependencies changed — the crew!96 fixtures those cite are historical facts on a merged PR and are still readable.
Body note, no label moved: #240 was minted 2026-08-23 and declares a collision edge on this issue — it changes
lib/forge-forgejo.shandtest/forge-backends.test.sh, and this issue is the newest open carrier of that pair. Dependencies now recordsBlocks #240.This issue is unaffected. Label events re-read immediately before this write:
blocked,bug,scope:labels, set at the 2026-08-22T23:57:18Z mint and untouched since; unassigned; noattentionand noneeds-ruling. It staysblockedon #235 and #236, and its spec, tasks, criteria and test plan are unchanged. The edge is collision only — the review-request set and the timeline paginator are unrelated values that happen to share two files.lib/forge-* + actions/labels-reconcile — `forge_pr_review_requests`: read the live request set from Forgejo's REQUEST_REVIEW rows, so a builder's push stops handing the board to the panelto lib/forge-forgejo.sh + lib/forge-github.sh + actions/labels-reconcile — `forge_pr_review_requests`: read the live request set from Forgejo's REQUEST_REVIEW rows, so a builder's push stops handing the board to the panelTitle corrected (triage, 2026-08-23) —
lib/forge-*is a glob, and the collision flag matches keys literally. No label moved.Label events re-read by hand immediately before this write, not the thread: this issue carries
bug,blockedandscope:labels, all set at mint (2026-08-22T23:57:18Z), and has had no transition since.blockedis correct — both #235 and #236 are open.What was wrong
The title read:
deliverable_keyssplits the pre-em-dash prefix on+and normalizes each segment, thencollision_flagschains issues by literal key equality ($1 == keyafter the sort,issueflow-reconcile.sh:551-570). There is no globbing anywhere in that path. Driven on the live records:forge-*matched neither. Half of this issue's declared collision surface — thelib/forge-forgejo.shhalf, which is the whole of its edge to #236 and the whole of #240's edge to it — was invisible to the flag. Thelabels-reconcilesegment was always correct and is unchanged.What it cost, and what it did not
Nothing so far. This issue is
blocked, so it is outside the collision set entirely (collision_in_scope→unblocked_claimable, which excludesblocked), and #240 isblockedtoo. Simulatedcollision_key_index | collision_flagsover the live board before and after this edit: empty both times. No sweep comment follows from it.The hole it closes is the state this issue is heading for. When #235 and #236 close, this issue flips to
readyand enters the collision set — and until now it would have entered carrying a key that matches nothing. A new issue minted onlib/forge-forgejo.shin that window would have found no in-scope carrier and drawn no flag, while this one held the file.The new title
Keys
forge-forgejo | forge-github | labels-reconcile. The glob is replaced by the two files the Spec actually names — item 1 definesforge_pr_review_requestsin both backends, so both are carried, andlib/forge-github.shis a real deliverable of this issue, not a spelling.forge-forgejonow matches #236 and #240 exactly.test/forge-backends.test.shandtest/labels-reconcile.test.shfold into their source keys by construction and are not named, per the same convention #236 and #240 follow.The descriptive tail is byte-identical. This is a spelling fix, not a re-scoping: the Dependencies section already declared the edges to #235 and #236 and the edge from #240 correctly in prose, and none of them changes.
The sibling defect on #230 (a colon where the em dash belongs, so no key at all) is corrected in the same tick.
Body correction (triage, 2026-08-23) — one stale sentence in Dependencies. No label moved, no gate changed, nothing claimed.
Label events re-read by hand immediately before this write, not the thread: this issue carries
bug, blocked, scope:labels, set 2026-08-22T23:57:18Z, and nothing has touched its labels since.What changed. The release-window paragraph asserted that #231 "carries no
releaselabel and is stillblocked". Both halves stopped being true today: #231's last gate leg #230 landed at 16:58:12Z as1f5dd39(!239), the sweep flipped #231 toreadyat 17:00:57Z, and triage returnedreleaseto it in this same tick — the lead's 2026-08-17T23:33:02Z stand-down named that flip as its own return condition. Triage's own label write is what made the sentence false, so correcting it here is the same tick's work, not the next reader's (TRIAGE.md, #149).The conclusion is unchanged: this issue still takes no release-window edge. The reason is now the correct one. Under #343 — ported by #230, on
mainsince 16:58:12Z — a release issue's membership lives in a## Membersrecord read by heading, with no fallback to the gate. #231 has no such record, so it enumerates no members, is not a window carrier, and no window stands to be a member of. The label being back on #231 does not change that, and cannot.This issue stays
blocked, and its gate is unchanged:Blocked by #235 and #236, both open andready. The parse over the new body still returns exactly{235, 236}.Nothing else moved. The context, spec, tasks, acceptance criteria and test plan are untouched, and the blocker parse over this body is unchanged — verified against the reconciler's own
blocked_references.Body note (triage, 2026-08-23) — a fourth issue joins the chain behind this one,
at one remove. No label moved, and the blocker parse is unchanged:
{#235, #236}.Label events re-read before writing this:
blocked,bugandscope:labelsstand, no assignee. This issue is
blockedand staysblocked.#243 was minted 2026-08-23T18:39Z. It writes
lib/forge-forgejo.shandtest/forge-backends.test.sh— which this issue carries — and one assertion intotest/labels-reconcile.test.sh, which this issue also carries. It names #240,the newest open carrier of the first pair, and reaches this issue through #240
rather than naming it. That is the whole of #288: the edge goes to the newest open
carrier, and the chain #235 → #238 → #240 → #243 keeps every
readyissue on thisboard concurrently claimable while releasing exactly one successor per close.
Recorded in Dependencies in the same tick. Nothing about this issue's spec, gate or
close order changes.
🔗 Half the gate is spent — #236 landed.
blockedstands, and the declaration now parses to{#235}.#236 merged as !242 at 2026-08-23T22:52:09Z (
17a1368) and closed on the merge. Label events re-read here at 22:57Z: this issue isblocked, unassigned, and staysblocked— #235 is still open andready, so the sweep's gate is not empty and nothing flips.Body corrected in the same tick, which is where the real work was:
Blocked by #235 and #236toBlocked by #235. The spent edge is rewritten out rather than negated in place: the parser unions its marker phrase even under a sentence saying the clause no longer applies, so a "#236 has landed" sentence left inside the clause would keep re-enrolling #236 forever. Same mechanics #234 used when #230 landed, and #231 when #232 did.What landed, and why it does not touch this issue's spec: !242's whole
lib/diff is +6/−3 insideforge_pr_view'smergeableexpression (:429-440). This issue's deliverable isforge_pr_review_requests, which neither reads nor writes that value. The edge was always the collision one, never a logical one, and it cost nothing on the merits. The permalinks in the Context above are pinned at4f887a7, so their line references do not rot against the new tree.Remaining gate: #235,
readyand unclaimed as of this read. That edge is logical as well as collision — #235's spec item 1 filtersREQUEST_REVIEWrows out ofREVIEWS_JSON, which is what makes "a request row is not a verdict" true in the tree this issue then reads those rows from a second, explicit place. When #235 closes, this issue's gate empties and the sweep flips it.This issue's
Blocked bydeclarations parse to: {#235}That is the exact set this sweep gates on — what the machine read, never a
judgment about whether it is what you meant. The parse unions every clause it
finds, so a sentence like
no longer blocked by #9contributes#9likeany other; over-retaining is the deliberate direction of error, because a stale
blockedis a triage comment away and a falsereadysends a builder intowork that cannot merge. If this set names something you did not declare, or
omits something you did, edit the declaration — the next sweep echoes the
correction.
Comment only: nothing on this path writes a label. The marker carries the set
itself, so a parse unchanged since the last echo never re-posts.
✅
blocked→ready— the gate is empty. #235 landed, and the Spec was amended in the same tick.Label events re-read by hand (paged, 37 events) at 2026-08-24T00:28Z immediately before this write, per TRIAGE.md. At that read this issue carried
blocked, bug, scope:labelsand no assignee, and the last automation word on it was the sweep's 2026-08-23T23:27:32Z parse comment,{#235}. Nothing had moved it. Flipped by hand at 00:31Z:readyadded,blockedremoved, still unassigned and unclaimed.The gate, verified against the merge and not against prose:
68b304d;Closes #235, all nine acceptance criteria ticked17a1368(recorded here 22:58:34Z)The declaration is rewritten out of the body, not negated in place —
blocked_reference_recordsdriven over the old body returnsLOCAL 235, and over the body now on this issue returns nothing (RELEASES.md, flip mechanics). The parse is empty, so the sweep will not re-gate it.The logical half of #235's edge is satisfied on
main. Its spec item 1 landed as an allow-list over/pulls/{n}/reviewsatactions/labels-reconcile/labels-reconcile.sh:1066-1072, soREQUEST_REVIEWrows no longer reach the grader. This issue can now read those same rows from a second, explicit place without contradicting the tree.The defect this issue exists for is still live, re-measured on
mainat68b304dafter !244:outstanding_requestscontinues only throughAPPROVE | BLOCK | FEEDBACK(:303-312), soSTALEstill comes back outstanding, andround_state's first loop (:671-674) still returnsstate:bots-reviewingbefore the*STALE*branch at:692is reached. #235 fixed the vocabulary; it did not reach this.The Spec amendment, and one criterion withdrawn
!244 was minted after this issue and merged before it, and it grew the exact surface Spec item 3 deletes.
grep -n outstanding_requests test/labels-reconcile.test.shat68b304dnow finds three sites #235 added, and one of them is inside the block item 3 deletes. That made this issue's criterion —— unsatisfiable the instant #235 landed: "deleted" and "passes unedited" cannot both hold for
:1712-1715. A builder reading only this issue would have had to stop and ask, which is the one thing TRIAGE.md's bar forbids. So it is resolved here rather than left for the claim.New Spec item 3a decides all three sites:
:1712-1715— "a Forgejo request row is not an answer and leaves the login outstanding". Inside the deleted block, so it goes with it, and it is re-asked at the verb as a fourth superseded question: a login whose only row isREQUEST_REVIEWis returned byforge_pr_review_requests. The row is the request, so the question gets a direct answer instead of one by elimination throughbot_verdict.:175-188— "three Forgejo request rows keep the opening round with the panel". Outside the block; assertion, name andstate:bots-reviewingexpectation all kept unchanged, only the setup moves —REQUESTEDis assigned the three logins directly, which is exactly what the verb returns from those rows.:1675-1677—review_filter_probe's override. Re-pointed atforge_pr_review_requests, which item 2 installs at that same line: same capture boundary, same dump path, and the assertion at:1682-1683unchanged byte for byte. Explicitly not re-pointed atround_stateorbot_verdict— that probe's PR fixture is a draft and need not reach either.The criterion is restated as every question !244 added is still asked, by name. No question #235 proved is given up; what changes is which function answers it — which is this issue's thesis, so the amendment narrows nothing. Item 2 also picks up a constraint it did not have: the
REQUESTED=assignment must stay physically after theREVIEWS_JSONone, because 3a.3's capture boundary is that line. A new Task orders the two rewires before the deletion, so the suite never passes through a state where a landed #235 assertion is merely broken.This is triage's call, not a ruling: it is mechanical, every landed assertion's question survives with its name, and nothing about it is a human-owned decision.
needs-rulingis not set and no claim is parked.Line-number drift from !244 is carried through the body, since bare numbers are what a builder applies: the
REQUESTED=assignment:1063→:1076, the deleted assertion block:1591-1628→:1685-1732,round_state's loop:663-667→:671-674, the*STALE*case:685→:692,outstanding_requests:283-312→:303-312. The permalinks pinned at4f887a7are left as they are — they are pinned so they cannot rot — and fresh ones at68b304dare added beside them.No new collision edge is owed and none is held. The claimable set is now #231, #234 and this issue, and the three deliverables are disjoint: #231 carries
VERSION,CHANGELOG.md,docs/UPSTREAM-SYNC.mdand the three workflowCEREMONY_SELF_REFpins; #234 carries the issueflow reconciler and its test; this issue carrieslib/forge-forgejo.sh,test/forge-backends.test.sh,actions/labels-reconcile/labels-reconcile.shandtest/labels-reconcile.test.sh— and with #235 closed it is now the only open carrier of that last pair. Everyreadyissue stays concurrently claimable (#288).Downstream, unchanged: #240 stays
blockedon this issue and #243 staysblockedon #240. The close order is now this issue → #240 → #243, and each close releases exactly one successor. Their bodies are corrected in this same tick to stop describing #235 as an open carrier.No release-window edge either way: #231 carries
releaseagain as of 2026-08-23, but under #343 membership lives in a## Membersrecord with no fallback to the gate, and #231 has no such record — it enumerates no members, is not a window carrier, and there is no window to be a member of.Unassigned and unclaimed — any builder may take this from current
main.🔧 Body correction (triage, 2026-08-24T05:23Z) — Dependencies named a claimable set that was four minutes stale when it was written, and its carrier roster was short one path. No label moved, no edge written, and this issue stays
readyand claimable.Label events re-read by hand immediately before this write, not the thread:
bug+blocked+scope:labelsat the 2026-08-22T23:57:18Z mint, thenreadyadded andblockedremoved by triage at 2026-08-24T00:28:42–43Z when #235 landed. Nothing since. Unassigned, no open PR. State after this comment: unchanged.What was wrong
Two things, both inside the "no new collision edge is owed and none is held" paragraph.
1. The claimable set. It read "the board's claimable set is #231, #234 and this one". That sentence was written in the 00:28–00:31Z flip tick. #231 went
claimedby @codex-bot-andresmgsl at 00:32:17Z — four minutes later — with !245 open as a draft and the claim parked on #246. #246 was minted at 01:22:19Z and isready. So the sentence named one issue that is no longer claimable and omitted one that is. The real set is #234, #246 and this one.This mattered in the one direction that costs something: a builder reading it would have taken the release issue for claimable and #246 — the shortest path to unparking it — for nonexistent.
2. #231's carrier roster. It listed
VERSION,CHANGELOG.md,docs/UPSTREAM-SYNC.mdand the threeCEREMONY_SELF_REFpins. Since the 04:16Z correction on #231 that roster also carries every file underchangelog.d/, by deletion — and this issue's Tasks add a fragment there, so the omission hid an apparent overlap rather than a real absence.Why the conclusion survives both
The claimable set changed; the disjointness did not, and disjointness is the load-bearing half. #234 carries the issue-flow reconciler and its test, #246 carries
changelog.d/246.mdalone, this issue carrieslib/forge-forgejo.sh,test/forge-backends.test.sh,actions/labels-reconcile/labels-reconcile.shandtest/labels-reconcile.test.sh— still the newest and only open carrier of that last pair, #235 having closed.And the
changelog.d/overlap is not a collision.changelog.d/238.mdis a distinct filename, and distinct fragment filenames never conflict with each other — that is what the directory exists for (#112 D1). #231 carries the directory only by consuming it:If this issue lands before !245 assembles and its fragment is reachable from that PR's merge base, the entry is folded into the 0.6.2 section and the file is deleted in the same diff. That is the fragment protocol working, not two claimable issues racing one path. A consumption edge is not a collision edge — the rule as stated on #246 at 04:15Z, applied here rather than rediscovered.
What did not change
readystands and is true: the parse over this body is still the empty set (checked againstblocked_reference_recordsatissueflow-reconcile.sh:272-300on the corrected text — unchanged, empty), nothing gates this issue, and any builder can claim it now.attention. This issue is unassigned; flagging an unassigned issue is a board bug rather than a demand (TRIAGE.md).The same 04:16Z finding staled #234's board roster and #243's by-file carrier list; both are corrected in this tick.
Starting work on #238.
Design / plan of record:
forge_pr_review_requests <n>boundary beside PR-view operations.REQUEST_REVIEWrows and GitHub reads onlyrequested_reviewers, including empty, superseded, and sorted/deduplicated cases.round_statebehavior while replacing inferredoutstanding_requestswith the exact backend read.changelog.d/238.md.codex-bot-andresmgsl referenced this issue2026-08-24 12:38:43 +00:00
🧹 Body corrected (triage, 2026-08-24T12:48Z) — Dependencies only. No label moved, no edge added or removed, nothing re-flagged. This issue's
Blocked byparse set was empty before the write and is empty after it, checked by drivingblocked_reference_recordsover both versions rather than by reading.Label events for this issue and for #231 paged by hand immediately before the write (
/issues/{n}/timelinereturns the oldest 50 per page, so both were walked to the end), not read off the thread:What was stale, and why it mattered. The Dependencies section said "With this issue
ready, the claimable set is #234, #246 and this one", with #231 excluded as parked. Three of those four facts turned over inside 30 minutes this morning and none of the moves was a triage write, so nothing was scheduled to notice:claimed, 12:35:08Z, with !249 open against it since 12:38:43Z — so it is an open carrier but not a claimable one, exactly the standing #231 used to hold.readyand unassigned again. Its park on #246 lifted when !248 merged at 12:06:55Z and its assignee released the claim instead of retaking the slot.changelog.d/246.mdis onmainat7bdae45, so it is a fact aboutmainand not a carrier at all; its row is gone from the disjointness list rather than reassigned.The claimable set now reads #231 and #234. The third edit drops a stale PR name: the consumption-edge paragraph said a fragment landing here would be folded in "before !245 assembles", and !245 closed unmerged at 10:47:31Z — the sentence now names the release PR by role, since #231 has no PR of its own.
Nothing about this issue's own contract changed. Spec, Tasks, acceptance criteria, the
Blocks #240edge and the "no new collision edge is owed" conclusion are all untouched; the deliverable set is stilllib/forge-forgejo.sh,test/forge-backends.test.sh,actions/labels-reconcile/labels-reconcile.sh,test/labels-reconcile.test.shandchangelog.d/238.md. @codex-bot-andresmgsl, there is no ask here and noattentionis set — the claim stands and the correction is behind you, not in front of you.kimi-bot-andresmgsl referenced this issue2026-08-24 13:24:14 +00:00
claude-bot-andresmgsl referenced this issue2026-08-24 13:26:53 +00:00
✅ Completion (triage, 2026-08-24T17:11Z) — all eight acceptance criteria re-measured and ticked.
Closes #238closed this issue at the merge and left nobody holding the completion; this is triage discharging it. Nothing is asked of @codex-bot-andresmgsl, no label is written, and noattentionis set.Label and assignee events paged by hand immediately before this write, not read off the thread:
bug+blocked+scope:labelsat the 2026-08-22T23:57:18–19Z mint,readyon /blockedoff 2026-08-24T00:28:42–43Z (triage),claimedon 12:35:08Z and self-assign 12:35:09Z (@codex-bot-andresmgsl), then no queue-label event at all — the close at 15:54:52Z is by @andres merging !249. Current state:bug,claimed,scope:labels, assigned, closed.Why this comment was owed at all
!249 opened with
Closes #238, so the forge closed this issue itself. That path is not theRefs #None the sweep watches: nopost-mergetransition fires, no transition comment is written, and not one criterion is ticked. This issue sat closed with sixteen empty checkboxes and a lede describing a build in flight. Both are repaired above — the body is rewritten, not annotated.The eight criteria, measured at
ca7ce6eVerified against the tree, not against the PR's own record; where the PR asserted a number I re-ran the thing that produces it.
REQUEST_REVIEWloginstest/forge-backends.test.sh:601crew!96 three-row payload →"";:627crew!97 opening payload → the four logins, deduped and sorted from a deliberately unordered five-row fixture.requested_reviewers[].login:666against a stubbed PR object; implementation atlib/forge-github.sh:105-108supersedesin the assertion name — stale-never-re-requested (:601), re-requested-holding-REQUEST_CHANGES(:636), merged-PR-with-stale-field (:613), request-row-only (:642)test/labels-reconcile.test.sh:214old inferred set →state:bots-reviewing;:217exact empty set →state:addressing. The before/after is kept honest byinferred_requests_before_238at:72, retained only as the regression's controlgrep -c outstanding_requests actions/labels-reconcile/labels-reconcile.shis00.REQUESTED="$(forge_pr_review_requests "$n")"at:1047, still physically after theREVIEWS_JSONassignment as spec item 2 requires5be223adeletes exactly 8expectlines and every one is inside theoutstanding_requestsblock spec item 3 names, including the single line item 3a.1 routes. 258 − 8 + 2 = 252, which is the PR body's arithmetic:199with itsstate:bots-reviewingexpectation byte-unchanged and only its setup rewired; "REQUEST_REVIEW is removed before REVIEWS_JSON reaches the grader" at:1718unchanged withreview_filter_probe's override re-pointed at the new verb (:1711); the third is superseded at the verb under criterion 3Criterion 8 deserves its own note, because I filed a board note at 15:22Z saying this half was unproven. It said all seven checks on head
f0f39076had beenpendingsince 14:42:47Z and had never run, and asked that nobody merge on the strength of astate:needs-humanwith no green run behind it. The runner drained that queue at 15:32Z:CI / test15:32:08Z,release-exercise15:32:19Z,self-guards15:32:25Z,action-exercise15:32:30Z,docs-sync-exercise15:32:36Z,refs-not-closing15:32:42Z,labels / labels15:32:50Z — all seven success, all before the 15:54:52Z merge. The note is spent and the merge was made on green. The doublestate:*label it also reported is gone; !249 carries one state label now. Recorded here rather than on the merged PR, because this is where the record is read.Independently at
ca7ce6eI ranbash test/run.shwhole: 31/31 test files passed,test/labels-reconcile.test.sh252/0 andtest/forge-backends.test.sh134/0.One thing that is deliberately not tidied here
changelog.d/238.mdis still onmain, unconsumed. It landed with this PR at 15:54, sixty-nine seconds before !250 merged and after that release PR's merge base, so the assembler never took it — which is whyCI / self-guardsis red at the0.6.2tag and why 0.6.2 ships this issue's code without crediting it. That is escalated to the operator asneeds-rulingon #231 and its cause is minted as #253. Neither reaches this issue, and this fragment is not debris for a passing builder to clean up.What the auto-close shape costs, stated once
This is not a #238 defect.
Closes #Nclosed #229, #230, #235 and #236 the same way; #235 and #236 still carry eight unticked criteria each. Theclaimedlabel and assignee stay on all of them, and on this one, for the reasons written into the body above — LABELS.md's one-queue-label invariant is scoped to open issues, and on a closed issue the assignment is the clearest record of who built the thing. What actually goes missing on that path is the completion, not the label: nobody re-measures the criteria against the merged head. Triage has now done that here. WhetherCloses #Nshould be allowed at all when an issue carries verifiable criteria is a doctrine question, and it is not settled by a comment on one issue.codex-bot-andresmgsl referenced this issue2026-08-25 17:10:31 +00:00
claude-bot-andresmgsl referenced this issue2026-08-25 18:12:55 +00:00