Uh oh!
There was an error while loading. Please reload this page.
fix(ci): count speculative-stack inheritance as one hit in merge-queue triage - #13107
Conversation
…e triage
The merge-queue-triage workflow already parsed the queue branch name and threw
away the half that matters: `/^gh-readonly-queue\/.+\/pr-(\d+)-[0-9a-f]{40}$/`
matched the 40-hex base commit without capturing it. That sha is the commit the
queue built the PR on top of, so under GitHub's speculative stacking a build
whose base IS another victim's queue head contains that victim's tree by
construction.
Without it, a single deterministic break ejects every PR queued behind it and
the anchor issue reports the queue depth as "N distinct pull requests" -- loudest
in exactly the case carrying the least information.
Capture the sha, index every merge_group run already fetched for the 24h counts
by `runId -> {base, head}`, and union victims whose base equals another's head.
Zero additional API calls: `recent` and `run.head_sha` were both already in hand.
The trigger stays `a.prs.size >= 2`. What changes is the reporting -- the anchor
now says "3 pull requests ... 1 independent hit", marks each row root/inherited,
and explains why the raw count overstates. A victim whose run is not in the
window index stays its own group: "cannot prove downstream" reads as
INDEPENDENT, never as same-stack.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw…eue-stack-inheritance
Uh oh!
There was an error while loading. Please reload this page.
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33237340137 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
os-elon
commented
Aug 29, 2026
Queue ejection triaged — not this PR's failure. Re-queued once, and this is the one re-run.
The failure, and why it is not oursQueue build 33237340137 went red on This PR's diff is two files, read from the diff rather than from the body: ⇒ it touches nothing under
What I did, and what I will not do againRe-queued once. Per this lane's standing rule, a re-run is legitimate only to confirm a failure that names a service the diff does not touch — and at most once in total. This is that one. ⛔ If it fails again with the same signature, I will not re-queue a third time. It would then be a real, reproducible failure in ⭐ Worth recording, because the recursion is exactThis is the PR that teaches merge-queue triage to stop counting speculative-stack inheritance as distinct victims — and it was ejected by a failure in a package it does not touch. That is the same family as its own subject: the queue runs the full suite while PR-side CI runs the affected subset, so a queued PR can be reddened by code it never went near, and the raw victim count says nothing about ownership until someone checks the diff by hand. Two seats spent real time deriving exactly that on #12859; this PR is the fix for the count, and it just paid the tax the count hides. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#12933
The workflow already parsed the merge-queue branch name and threw away the half
that mattered:
One capture group — the PR number. The 40 hex digits were matched but not
captured, and they are the commit the queue built the PR on top of. Under
GitHub's speculative stacking every PR queued behind a broken one inherits its
tree, so for a deterministic failure the anchor issue's "N distinct pull
requests" measured queue depth, not flakiness — and it kept climbing until
the owner landed a fix. The signal was loudest exactly where it carried the
least information, and every reader had to re-derive that by hand.
The change
QUEUE_REFconstant, used both at theentry check and by the new run index.
runId -> {base, head}from themerge_groupruns already fetchedfor the 24 h counts, plus this run. Zero additional API calls —
recentand
run.head_shawere both already in hand, which is what makes thisaffordable in a job whose own header calls itself rate-limit-shaped.
independent hits beside the raw count.
⛔ The trigger is untouched — still
a.prs.size >= 2. What changed is thereporting. Raising the bar to "2 independent hits" would silence a real
deterministic break that has already eaten the whole queue behind it, which is
the case a reader most needs to see.
equals another victim's head. Never a base prefix, never time ordering — both
would fuse genuinely independent hits into one stack, trading this limb's false
positive for a false negative, which is strictly worse. A victim whose run is
not in the window index stays its own group: "cannot prove downstream" reads as
INDEPENDENT, never as same-stack, and the count of those is reported.
The anchor issue now reads
3 pull requests ... 1 independent hit, marks eachrow
root/inherited, and says why the raw count overstates instead of justprinting a smaller number. The issue TITLE is deliberately unchanged: it is a
second identity channel for finding an open anchor, and a body is the one
channel GitHub is known to rewrite.
Verification
Both gates run the shipped bytes — the harness extracts the script out of
the YAML with a real parser and executes it as
actions/github-scriptdoes — sothe changed YAML is the subject, not a copy.
check-merge-queue-triage-outcome.mjs... --self-testNew scenario A12 is the recorded incident re-scored: three victims whose
queue bases chain (
pr-12851's base ISpr-12843's head;pr-12855's base ISpr-12851's head) must report as one stack with#12843as root. Newscenario A13 is the symmetric false-negative guard: three genuinely
unrelated victims must still report as three.
Two ablations, and a new
keepGreenfield so each states what must not move:that proves A12's red comes from the stack test and not from the anchor body
merely changing shape.
base === head. A13reds. A12 was predicted to be the control here and was not — it reds
too, because co-occurrence marks
#12843inheritedas well, and a stackwhose every row is inherited names no owner to start from. That the root
marker is the sharper detector here, not the count, is recorded in the table
rather than smoothed over.
The naming convention has three independent witnesses, checked on today's tree:
scripts/pm/check-governed-queue-guard.mjs, the workflow's own comment, and alive measurement —
git ls-remoteshowedgh-readonly-queue/main/pr-13082-d48929efe794b467c169825ecb68a2d015f09d8dat refsha
b15d260d1..., andd48929efe7wasorigin/main's tip at that moment. Thesuffix is the base; the ref's own sha is the head.
What these checks could not exercise: the YAML's action wiring (
on:,permissions:,retries:) and the real API payload shapes.head_shaon anActions run object is witnessed in-repo by
scripts/pm/ci-failure.mjs:1260(
GET /repos/.../actions/runs/{id}returnshead_sha). If that field were everabsent the index would simply stay empty and every victim would read as
independent — the pre-change behaviour, so the failure direction is safe.
Gate family derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(first derivation reported STALE TREE and still exited 0; re-derived after
syncing — identical list). 30 of 31 green at
3466786098;check-test-completeness.mjsis NOT MEASURED — it takes a turbo test logargument and answered with its usage line, which is an invocation error, not a
red gate.
skip-changesetderived rather than asserted:pr-automation.ymlfires on everypull request with no
paths:filter,changeset-check's only exemptions arethat label and the changesets release PR, and
.github/labeler.ymlnever appliesit automatically. A workflow-plus-scripts diff releases nothing, so the label is
the only available route and is applied on this PR.
Generated by Claude Code