Uh oh!
There was an error while loading. Please reload this page.
fix(ci): merge-queue-triage retries delivery, and a refused post no longer destroys the triage - #9574
Merged
Merged
Conversation
…onger destroys the triage The triage comment is the machine-readable signal the PM dispatch loop keys on, and the cross-PR flake evidence lives in it — but it was posted through a bare `github-script` call with no retry and no handler, so a transient GitHub API failure killed the job and took the diagnosis with it: `body` exists nowhere but the failed request, and the run page was left showing `Unhandled error: HttpError` and nothing else. Declare the retry policy on the step (`retries: 3`, with 403 removed from the exempt list because GitHub answers a secondary rate limit with 403 as well as 429, and this job paginates jobs, logs and 24 h of runs). Nothing is swallowed: a spent retry still throws, so detection failures fail exactly as before. On a refused delivery, write the whole computed triage into the job summary — where it outlives the request and can be pasted onto the PR — and then fail. Failing is deliberate and diverges from the sibling fix in docs-drift-check.yml (#9373/#9423): this is a `workflow_run` job whose conclusion is on no check list, in no required context and gates nothing, so red costs nothing while a green that quietly delivered nothing recreates the silence one layer further in. That buys the invariant: green ⇔ the triage comment is on the PR. The de-duplication listing degrades to at-least-once instead: its marker is scoped to one run id, so a duplicate is inert while a miss loses the signal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
This was referenced Aug 18, 2026
os-steve
marked this pull request as ready for review
August 18, 2026 12:01
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9424
.github/workflows/merge-queue-triage.ymlposted its entire deliverable through a bareactions/github-script@v9call with no retry and no handler. github-script routes any throwfrom the inline script to
main().catch(handleError)→core.setFailed, so one transientanswer from the issues-comments endpoint killed the job — and took the diagnosis with it,
because
bodyexists nowhere but the failed request. The run page was left showingUnhandled error: HttpErrorand nothing else.That loss is not the same as the sibling case. This comment is not a courtesy: it is the
machine-readable signal the PM dispatch loop keys on, and this file's own header says the
cross-PR flake evidence lives in these comments.
What changed
1. The retry policy is declared on the step, not hand-written.
actions/github-scripthas carried these two inputs since v6; they drive octokit's retryplugin, which re-issues any request whose status is not exempt, plus network-level failures.
That is exactly the transient class the sibling ruling settled — 5xx, 429, secondary-rate-limit
403, network codes — with no classifier of my own to maintain. 403 is removed from the
action's default exempt list (
400,401,403,404,422) deliberately: GitHub answers a secondaryrate limit with 403 as well as 429, and this job is rate-limit-shaped (it paginates the run's
jobs, pulls up to four job logs, then paginates 24 h of merge_group runs). The price is that a
genuine permission denial now takes four attempts to fail instead of one. It still fails.
Nothing is swallowed by this: a spent retry still throws. Detection failures fail exactly as
before — the job harvest and the 24 h history are above the handler and still take the job
red, with
Unhandled error:unchanged.2. A refused delivery no longer destroys the triage. The computed comment is written into
the run's job summary — where it outlives the request, is readable without a re-run, and can be
pasted onto the PR by hand — with a line telling the reader how to tell platform weather (5xx /
429 / network, re-run it) from this repo's own bug (422 over-long body, 403 permissions, re-run
will not help).
3. And then it still fails. This is a deliberate divergence from PR #9423, which kept the
docs-drift job green. There, the job's conclusion is a check on the PR and its comment is
advisory, so a red costs a reader's attention for nothing. Here the job is
workflow_run: itsconclusion appears on no check list, is in no required context (verified against
scripts/check-required-contexts.mjs— this workflow has no entry) and gates nothing. So redcosts nothing, while a green that quietly delivered nothing would recreate this card's silence
one layer further in. Failing buys a total invariant something can be keyed on:
A non-fatal warning was the shape the dispatch suggested. It was not taken because a warning
annotation lives only on a
workflow_runrun page nobody is watching, whereas a failed run isdurable, queryable (
gh run list --workflow "Merge Queue Triage" --status failure) andnotifying. If the maintainer prefers uniformity with #9423 over the invariant, the change is
one line —
core.setFailedtocore.warning.4. The de-duplication listing degrades to at-least-once. Its marker is scoped to one run
id, so a duplicate is inert (same run, same text, self-evident pair) while a miss loses the
signal entirely. PR #9423 chose the opposite for docs-drift, and correctly: that marker is
stable across runs, so posting blind there strands a second advisory the dedup then updates
forever alongside the first.
Dispatch assumptions, tested
continue-on-erroris still 0, and thecomment post is genuinely unguarded — but the card's
catch (error)\|catch (err)patternreported 0 catches while the file has one:
catch (e)at the job-log harvest. It does notcover the comment post, so the finding stands; the measurement quoted on the card does not.
changed the fix: this one only ever creates (docs-drift updates-or-creates); its marker is
per-run, not stable; and it makes four other API calls before the post, all of them
detection, which is why a blanket step-level tolerance was never an option here.
move and so was extracting it.
merge-queue-triage.ymlis aworkflow_runjob thatdeliberately never checks out (stated in its header), so a shared module would have meant
adding
actions/checkoutto a privileged-context job and inventing ascripts/ci/patternthe repo does not have, to share ~30 lines between two call sites. Using the action's own
declared inputs removes the duplicated rule instead of relocating it: there is no second
copy of the classifier, because this file writes none.
Validation — simulated, and stated as such
No real 503 was observed and none is claimed; these error branches cannot be triggered on
demand in real CI. Following the technique PR #9524 landed: a python yaml parser extracts the
shipped script — and the shipped
retries/retry-exempt-status-codesvalues — from thisworkflow file, and a harness executes it under a stubbed octokit and
@actions/core,reproducing github-script's own
main().catch(handleError)propagation and octokit's retrysemantics driven by the declared inputs. The harness is deliberately not in this diff: the
scaffolding is validation, not a product.
Baseline (
origin/main, which declares no retries) reproduces the card's exact signature:create-503-onceandcreate-503-persistentboth →FAILURE: Unhandled error: HttpError: No server is currently available to service your request.,comment not posted, nothing preserved anywhere.
After, 14/14 scenarios as designed:
Unhandled error:)Unhandled error:)The load-bearing rows are the two
detect-*ones (tolerance never reaches detection),create-422andcreate-403-plain(this repo's own bugs still fail), andlist-503-persistent(where the baseline lost the comment entirely and this now delivers it).
Gates
Union re-run after the final commit
9647f89cd, all exit 0:pnpm check:node-version,pnpm check:required-contexts,pnpm check:shard-attestation,pnpm check:workflow-status-functions,pnpm check:nul-bytes,node scripts/check-shard-attestation.mjs. Re-derivation withnode scripts/pm/dispatch-gates.mjson the actual changed path added no family beyond thedispatched list. Control-character self-scan on the changed file: clean.
Workflow-only change, nothing published —
skip-changeset.Generated by Claude Code