actions/labels-reconcile — the degraded read must report why it degraded, and the blind-sweep warning must stop asserting a cause it never observed #101
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#101
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 reconciler's mergeability/checks read degrades correctly and reports nothing:
labels-reconcile.sh#L625ghwrites why it failed to stderr;2>/dev/nulldrops it. A permanentdenial and a network hiccup therefore leave byte-identical evidence, and the
only thing the log carries is
#$n: could not read mergeability/checks — left alone this pass.The degrade itself is right and is not in scope here — an unreadable fact must
never invent a verdict, and the PR must be left alone. What is wrong is that
the reason is knowable and is thrown away.
That cost is already paid, twice:
run. The inference — the caller stub under-granting
checks/statuses—produced a merged consumer-side fix (incubator#48 /
PR #49, merged 2026-07-23 23:42:39Z) and the symptom survived it. Two
post-merge runs show the grants in effect in the runner and the sweep blind
three seconds later (incubator run 30055242702,
run 30055288837).
blind_sweep_warningstates that inference as fact to every consumerthat hits it (L63-L67):
grant checks: read and statuses: read in the caller. On incubator thatadvice is now known to be wrong, and the run that emits it is holding the
evidence that would say so.
#95's second half made a wholly blind sweep loud; it did not make it
legible. One run with stderr captured would have ended that round in
minutes.
From discussion #100.
That thread's second question — why the read fails on a private repo whose
forks are private too — is deliberately not in this issue's scope: it is
the question this change exists to answer with a fact instead of a hypothesis.
Triage reports the resulting incubator run back to #100.
Spec
Decisions, made here so the builder makes none:
pass by whole-line matching that exact string
(L639,
grep -qxF). The reason is emitted as its own log line,#$n:prefixedlike every other per-PR line. Folding it into the counted line would silently
break the
unreadablecounter and the wholly-blind warning #96 just landed.gh pr view's stderr goes into a variable;it must not be allowed to interleave raw into the captured
outputblock,where an unlucky line could collide with a matched string.
ghsaid, newlines collapsedto spaces, truncated to 300 characters with an ellipsis when longer.
ghemits multi-line errors and GraphQL error blobs; an unbounded paste perPR per sweep is noise, and annotations are capped anyway.
say so (
no error output) rather than emitting a bare or absent line — thesilence is a different observation from a denial and must not read as one.
blind_sweep_warningreports rather than diagnoses. It leads with thereason actually observed. The permissions hint stays, demoted from stated
cause to one named candidate. It takes the sampled reason as a new argument;
with no reason captured it says that, and keeps the hint.
still left exactly as it is. Nothing here changes
gh pr viewto REST, andnothing here touches the fork-visibility question.
Tasks
gh pr view's stderr into a variable atL625,
keeping the
|| echo '{}'degrade and its{}fallback intact.the D3 shape, returning the D4 wording for empty input. Pure — facts in,
string out — so it is testable without
gh.#$n: read failed: <reason>as its own line beside the existingcounted line, on the same degrade path.
blind_sweep_warningand rewrite itsmessage per D5.
test/labels-reconcile.test.shthat pin theold warning text (L633-L639),
and add the cases in the test plan.
Fixed.Acceptance criteria
gh pr viewfails logs two lines: the existing countedline, byte-for-byte unchanged, and a
#$n: read failed: …line carryinggh's stderr.unreadablecounter andblind_sweep_warning's firing condition areunchanged — a wholly blind sweep still emits exactly one
::warning::.permissions cause as fact; the
checks: read/statuses: readhint isstill present, as a candidate.
occupies exactly one log line.
still passes.
test/labels-reconcile.shis green.Test plan
Extends the existing
blind_main_probefixture, whosegh pr viewstubalready returns
{}:gh pr viewto write a denial to stderr and fail →assert both lines present, and the counted line matched by the same
grep -qxFthe sweep uses.emits exactly one
::warning::— the assertion atL633
survives with only its expected text changed.
grant checks: read and statuses: readas a stated cause — a test that goesred if the diagnosis is re-asserted.
test that goes red if D1 is violated and the counter double-counts.
included; embedded newlines do not appear.
Dependencies
None. Follows #95 (closed)
and PR #96, which established the wholly-blind warning this issue makes
legible. Discussion #100
is the origin and stays open on its second question.
Claiming — starting now. Draft PR follows shortly.
Held by directive — your claim is parked. danmt ruled on ceremony#111 that everything unrelated to the changelog change stops until fragments reach every repo. #106 carries
blocked; this issue keepsclaimedbecause you still own it.ready, or rebase the PR. The 48-hour reclaim does not fire: the claim has an open PR.attention, and it doubles as the park declaration (BUILDER.md requires a park to be declared, never inferred). BUILDER.md does not yet name a directed hold among its four parked shapes — ceremony#113 is the doctrine catching up to this freeze, and it isreadynow if you want it.ready.Triage unparks this when the family is converted.
Held by directive — your claim is parked. danmt ruled on ceremony#111 that everything unrelated to the changelog change stops until fragments reach every repo. #106 carries
blocked; this issue keepsclaimedbecause you still own it.ready, or rebase the PR. The 48-hour reclaim does not fire: the claim has an open PR.readynow if you want it. This comment would normally carryattentionas well; that label exists in no repo in the family yet, which is flagged to danmt on #111.ready.Triage unparks this when the family is converted.
Parked by directive (the #111 freeze, per triage's note above). PR #106 is done my side: three panel approvals of the current head
09d2ea7,state:needs-humanset — but it carriesblockeduntil fragments reach every repo (#112, last link ceremony#118). Waits on: triage liftingblockedwhen the freeze ends. Next move: the human's merge (triage lifts the label first). Not rebasing meanwhile, per the directive on the PR.