You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
merge-queue-triage bot: excerpt the failure REASON line, and aggregate same-signature ejections across PRs into one anchor issue (2026-08-20 queue-incident layer ③) #10128
Filed by the spec seat on the maintainer's instruction (2026-08-20 live chat, verbatim: 「立卡」, accepting layer ③ — ranked highest-leverage of the four: it shortens the diagnosis loop for the NEXT unknown incident, whatever its cause). Provenance: the #4859 triage workflow this extends; incident thread #10115 · #10112 · PR #10120.
The two diagnosis failures this automates away, both measured tonight
The excerpts omitted the failure REASON. The bot's log extraction showed FAIL src/dev-plugin-security-enforcement-warning.test.ts (and on earlier ejections, stale-looking assertion lines) but not the deciding line — Test timed out in 5000ms vs AssertionError: …. A first responder locally reproduced a DIFFERENT failure of the same file (unbuilt closure ⇒ assertion) that matched the excerpt's shape, and filed a confidently wrong diagnosis (finding: #10036 的告警测试依赖兄弟包 dist 却未声明构建边 —— merge queue 全量 shard 随缓存状态非确定性红,已弹出一个无辜 PR(#10105) #10112, "missing build edge"). One full dispatch was spent falsifying it — the actual CI logs contained zero assertion failures and only timeouts, a distinction one grep in the bot would have surfaced.
The change, two limbs on the existing workflow (scripts/ + the #4859 workflow)
Reason line in every excerpt: alongside the FAIL line, grep the job log for the matching failure-reason line (Test timed out in … / AssertionError / Hook timed out in … / first Error: after the FAIL) and print it labeled. One extraction addition; the excerpt becomes self-triaging for the timeout-vs-assertion split that burned tonight.
Cross-PR signature aggregation: key = failing test file path (per queue build). When the same key appears in ejections of ≥2 distinct PRs within a rolling 24h, the bot files ONE anchor issue (or refreshes the existing open one — dedup by a stable marker in the issue body, the same idempotency idiom the bot's own <!-- merge-queue-triage:… --> markers use) listing every affected PR/build, and links it from each subsequent victim's comment. Single anchor = single place for the fix conversation, and the victim count is visible instead of re-derived.
Boundary, explicit: the bot still decides nothing — it names and aggregates; humans and seats still triage. No auto-requeue, no auto-quarantine (gate-weakening stays a human act).
Dedup: no existing card extends the triage bot (searched: merge-queue-triage / signature aggregation / failure reason). The bot's own workflow #4859 is its anchor and stays open for its own scope.
Claim: PM loop round 30 (serial — one baton at a time)
Session: session_01XqDQYVU5smx29ts9pAErja
Branch: claude/issue-10128-queue-triage-reason-anchor
Worktree: objectstack-issue-10128
Domain: domain:devx
File surface: .github/workflows/merge-queue-triage.yml and the scripts/ it calls (scripts/pm/ci-failure.mjs is the log-extraction seam; scripts/check-workflow-status-functions.mjs / scripts/pm/check-half-states.mjs are its gates) (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus — node scripts/pm/dispatch-gates.mjs --tier run this round over the six merge-queue-triage-bearing paths at origin/mainc766ec3600: "no path-derived mandate: the surface hits none of the 1 declared glob(s) … This line is a FLOOR, never a clearance." Matches the card's own suggested M, opus.
Clause-②: no — the card draws the boundary itself and I hold it: the bot names and aggregates, decides nothing (no auto-requeue, no auto-quarantine — gate-weakening stays a human act). No contract's accept/reject behaviour moves and no public surface widens. The dev re-declares against its actual diff.
Serial constraints cleared: swept all 5 open PRs — none touches merge-queue-triage.yml, ci-failure.mjs, check-half-states.mjs, check-workflow-status-functions.mjs, or the cross-repo closer. Two sibling cards named for read-coupling, both still unclaimed in pm:queue, neither in flight: #9898 (ci-failure.mjs's --self-test runs nowhere in CI) and #9966 (ci-failure.mjs's transport probe carries the same false green as #9946). Both land in the same file this card's limb ① extracts from — whoever moves second rebases. Limb ①'s reason-line extraction should not depend on either landing.
Dev report for #10128 — structured, machine-parsed by the PM.
{
"issue": 10128,
"status": "done",
"branch": "claude/issue-10128-queue-triage-reason-anchor",
"pr": "https://github.com/objectstack-ai/objectstack/pull/10140",
"premise_still_valid": true,
"summary": "Clause-②: no. Declared against the actual diff: no spec/Zod schema, no package export, no metadata key and no published-package source is touched; the subject is one internal CI workflow's comment text plus a new repo gate. The bot's accept/reject behaviour is unchanged — it still comments on exactly `merge_group` + `conclusion == failure`. Two things a reviewer could weigh differently and should see explicitly: the job now declares `issues: write` (limb ② files and refreshes an issue), and the bot gains the ability to CREATE issues on the live repo. Neither is a contract accept/reject move by my read, but both are new capability on a shared repo, so if the PM's Clause-② reading covers 'new write capability' rather than 'contract surface', flip it and hold the PR — I will not argue.\n\nIMPLEMENTED. Limb ①: the extractor looks ahead from each FAIL line for the deciding line (Test/Hook/Teardown timed out in Nms | AssertionError | the first `Error:` after it), stops at the next FAIL so no failure borrows its neighbour's reason, and prints it labelled under its FAIL line. Three coupled corrections in the same seam, each with evidence: the three cross marks must now sit at line START (four of twelve lines in the excerpt the bot really published on PR #10008 were `stdout |` noise admitted only by the multiplication sign inside a test TITLE); a line reported as a reason is no longer re-printed as its own excerpt line (12 excerpt lines -> 9 on the real assertion log); excerpt lines are ANSI-stripped. Limb ③: each ejection is keyed by the failing test FILE PATH and records that sighting in its own comment as a machine-readable marker; the next ejection reads 24h of those markers back (bounded, newest-first, 5 pages) and at >= 2 DISTINCT PRs files ONE anchor issue per key or refreshes the open one, matched on a stable per-key body marker, then links it from the victim's comment. BOUNDARY HELD: no requeue, no quarantine, no label on anybody's PR, no skip written anywhere — and it is enforced rather than promised, because the harness records-and-throws on ANY unmodelled API and `judge` fails the scenario on that record, so a future call that requeues or labels fails the battery with no scenario having anticipated it.\n\nLIMB ③ EVIDENCE (all four cases the dispatch asked for, all under doubles). POSITIVE (A1): ledger carries KEY@PR#10105, this run is PR#10008 -> exactly 1 issues.create, 0 issues.update; the created body carries the per-key anchor marker and table rows for #10105 and #10008; the victim comment links the new issue number; the comment carries this run's own sighting marker. REFRESH-NOT-DUPLICATE (A2): driven as a PAIR — run 2's world is built from run 1's ACTUAL output (the issue it created plus the comment it posted), never a hand-written marker, because a hand-written one proves each half while leaving the two runs free to disagree about its spelling. Run 2 is PR#10003: 0 creates, 1 update against the SAME issue number, refreshed body lists #10105, #10008 and #10003, and the third victim's comment links the same anchor. NEGATIVE, different keys (A3): this run keys file A while the ledger holds file B already seen on TWO distinct PRs -> 0 creates, 0 updates, and the comment says the key was seen on this PR alone; the deliberately-poisoned ledger is what makes the aggregation prove it only aggregates keys THIS ejection hit (mutation M6 flips it to aggregate every ledger key and A3 goes red). NEGATIVE, same PR twice (A4): ledger holds the same key on PR#10008 under two different run ids -> 0 creates, 0 updates (mutation M5 counts RUNS instead of DISTINCT PRs and A4 goes red). Bonus (A5): a redelivered workflow_run writes nothing at all and does not even read the ledger.\n\nHOW THE LIVE REPO WAS KEPT OUT OF IT. The gate has no network of any kind: `github` is a Proxy over hand-written stubs, `issues.create` / `issues.update` / `issues.createComment` are functions that append to an in-memory call ledger and return fabricated numbers, and every API the harness does not model is recorded out-of-band and thrown so no scenario can absorb it. Nothing in a gate run resolves a hostname. Corpus capture touched the live repo through READS only (get_job_logs, issue/comment reads) plus one local `vitest run` in my own worktree. The complete list of live WRITES I made this task: the branch push, the draft PR #10140, the additive `skip-changeset` label POST, finding #10141, and the report comment. No test run created an issue or commented on any PR.\n\nSURFACE. Beyond the dispatch's declared surface I added two things, both declared here rather than widened silently: `.github/workflows/lint.yml` (the wiring — a gate nobody runs is this repo's own recorded anti-pattern, and dispatch-gates.mjs now discovers the new family from that step) and `scripts/fixtures/merge-queue-triage/` (the corpus + a README stating each file's provenance). No published package source. `skip-changeset` applied additively (POST /labels, never a wholesale PUT) after reading the labeler bot's `documentation`, `ci/cd`, `size/xl`; read back as [\"documentation\",\"ci/cd\",\"size/xl\",\"skip-changeset\"].\n\nWHERE THIS DISPATCH WAS WRONG, on the record. (1) FALSE: `scripts/pm/ci-failure.mjs` is NOT 'the log-extraction seam limb ① lands in'. The triage bot's log extraction is entirely inline in `.github/workflows/merge-queue-triage.yml`'s github-script block; `ci-failure.mjs` is an unrelated seat-facing CLI whose own header says raw job logs are unreachable from an agent container. Limb ① landed in the workflow and I did not touch `ci-failure.mjs` at all — so #9898 and #9966 are not read-coupled to this card in any direction, and neither blocks nor is blocked. (2) SHARPER THAN THE CARD: 'the excerpts omitted the failure REASON' is only half true, and the half it misses is the mechanism. `AssertionError` was ALREADY a token of the old FAIL matcher, so an assertion DID reach the excerpt — as a detached, unlabelled line — while a timeout produced NOTHING. Measured on the two real captures: BEFORE on the assertion log shows three AssertionError lines, BEFORE on the timeout log shows none. That asymmetry is exactly the trap: a reasonless excerpt reads like the assertion-shaped excerpts a responder has seen before, which is how #10112 got filed. (3) OVERSTATED: the dispatch calls `check-half-states.mjs`, `check-workflow-status-functions.mjs` and `check-cross-repo-closer-outcome.mjs` the bot's 'gates'. None of the three gates it in any behavioural sense — each merely MENTIONS merge-queue-triage in prose (an H12 latency note; a folded-`if:` specimen; a marker-idiom contrast). Before this PR the bot had NO gate at all; that is what `scripts/check-merge-queue-triage-outcome.mjs` now is. (4) Minor and correct-at-the-time: `origin/main` was `c766ec3600` when I re-derived it; it has since moved to `a659896b4d`, with ZERO overlap with my file surface (checked file-by-file), so I left the base alone rather than merging noise into a draft.\n\nCI at report time: 24 check runs on head e4d81f03ca — 12 success, 9 skipped, 3 in_progress (`Lint & Repo Gates`, `TypeScript Type Check`, `Test Core (1/3)`), ZERO red. Reporting now per the draft-PR-time contract; `Lint & Repo Gates` is the job that runs the new gate and had not converged.",
"tests": "All at head e4d81f03ca (the final commit; the union below was run on that tree). Exit codes captured before any pipe.\n\nnode scripts/check-merge-queue-triage-outcome.mjs --self-test -> EXIT 0: \"✓ check-merge-queue-triage-outcome --self-test: 78 assertions, 15 mutations of the shipped script each driven to red.\"\nnode scripts/check-merge-queue-triage-outcome.mjs -> EXIT 0: \"check-merge-queue-triage-outcome: OK (61 assertions over 18 scenarios, driving the 21135-char script extracted from .github/workflows/merge-queue-triage.yml against real captured logs).\"\n\nLIMB ① BEFORE/AFTER, verbatim, both directions, on real captured logs of the incident's own file (packages/plugins/plugin-dev/src/dev-plugin-security-enforcement-warning.test.ts at 7552e03375, unbuilt closure — the exact condition #10112 measured). BEFORE = the script extracted from origin/main; AFTER = the script extracted from HEAD; same doubles, same fixture bytes.\n TIMEOUT, BEFORE: 'FAIL src/dev-plugin-security-enforcement-warning.test.ts > ... > bail #1 (no objectql/metadata service): the three' (and no reason line anywhere — 8 excerpt lines, none naming why)\n TIMEOUT, AFTER : 'FAIL src/dev-plugin-security-enforcement-warning.test.ts > ... > bail #1 (no objectql/metadata service): the three'\n '↳ 失败原因: Error: Test timed out in 1ms.'\n ASSERTION, BEFORE: 'FAIL src/dev-plugin-security-enforcement-warning.test.ts > ... > bail #1 (no objectql/metadata service): the three'\n 'AssertionError: SecurityPlugin.init() ran: expected false to be true // Object.is equality' (present, but DETACHED and UNLABELLED — see correction 2)\n ASSERTION, AFTER : 'FAIL src/dev-plugin-security-enforcement-warning.test.ts > ... > bail #1 (no objectql/metadata service): the three'\n '↳ 失败原因: AssertionError: SecurityPlugin.init() ran: expected false to be true // Object.is equality' (9 lines, was 12 — the duplicate is gone)\n The two FAIL lines above are BYTE-IDENTICAL across the two logs; scenario E3 asserts that identity rather than describing it, so the corpus cannot drift into two obviously-different logs and leave the claim unproven.\n THIRD FIXTURE, the excerpt the bot really published on PR #10008 for queue build 32333709633, read back through the API: BEFORE = 7 lines, four of them `stdout |` noise and raw ANSI, no reason anywhere; AFTER = 3 FAIL lines, noise gone, each carrying '↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行)'. That last line is the measured BEFORE stated honestly: the reason was never in what the reader saw.\n\nANTI-NO-OP LIMB, inside the bot itself (not only in the gate): unreadable logs / zero derivable keys emit a core.warning titled 'Queue-signature aggregation had nothing to key on' and the comment says 这一轮没测到 (an absent MEASUREMENT, not an absent signal) — scenario A6; a truncated 24h ledger warns and reports its counts as 下界 (lower bounds) — A7; an anchor whose absence could not be established is NOT created — A8. Mutations M9/M10 downgrade the first two warnings to info and A6/A7 go red.\n\nRepo gates, derived from the ACTUAL changeset with `node scripts/pm/dispatch-gates.mjs` (no paths passed; it took the 7-path change set from the merge base itself) and run after the final commit:\n pnpm check:cross-package-test-inputs EXIT 0 'All 57 self-test cases passed. / OK: 12 package(s) read outside themselves, all declared'\n pnpm check:node-version EXIT 0 'OK (30 setup-node step(s) across 27 workflow(s), all on Node 22)'\n pnpm check:required-contexts EXIT 0\n pnpm check:shard-attestation EXIT 0 '92 assertions ... 2 aggregate gate(s) count 3 declared leg(s)'\n pnpm check:type-check-coverage EXIT 0 'OK — 64/77 workspace packages type-checked'\n pnpm check:workflow-status-functions EXIT 0 '27 workflow file(s), 46 job(s), 24 job-level if: expression(s)'\n pnpm check:nul-bytes EXIT 0 '6380 text file(s) ... no raw ASCII control bytes'\n node scripts/check-cross-repo-closer-outcome.mjs (+ --self-test) EXIT 0 (adjacent bot; lint.yml is touched, and its self-test pins that wiring)\nDECLARED NARROWING: `pnpm check:type-check-debt` was NOT run locally. It refuses to re-measure without a full built package closure ('55 workspace dependenc(ies) ... have no built type entry point on disk') and this diff contains ZERO TypeScript and no package.json (verified: `git diff --name-only origin/main...HEAD | grep -cE '\\.(ts|tsx|mts|cts)$'` = 0, package.json = 0). It appears in the derived list only because .github/workflows/lint.yml is touched. Building the whole package graph to re-prove an unrelated ledger is not a cost this card should spend on a shared container; CI runs it on a built tree.\n\nByte discipline: the fixtures store ESC in its escape spelling (a raw 0x1b is a check:nul-bytes failure) and the harness materialises it; the workflow builds ESC with String.fromCharCode(27) rather than typing an escape into YAML. `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over every touched file: no match. Worth recording that this bit me once mid-task — an ESC materialised into a heredoc I was writing for the fixtures README, and the tool layer refused the command; the README is now generated with the escape spelling constructed rather than typed.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #10141: raw CI job logs ARE reachable from an agent seat via the GitHub MCP server's get_job_logs (tail window), while direct egress to the log archive is still refused with CONNECT 403 — so ci-failure.mjs's header claim that raw logs are 'NOT a foundation any seat-facing tool can stand on' is true only of direct egress. Both halves measured today. Labelled `finding`, unassigned, adjacent to #9898/#9966."
]
}
Correction to my claim comment above — posting it here rather than editing in place, so the wrong version stays readable.
My Serial constraints cleared: line named #9898 and #9966 as read-coupled to this card, on the premise that scripts/pm/ci-failure.mjs is "the log-extraction seam limb ① lands in". That premise is false. The triage bot's log extraction is entirely inline in .github/workflows/merge-queue-triage.yml's github-script block; ci-failure.mjs is an unrelated seat-facing CLI and PR #10140 does not touch it. #9898 and #9966 are not coupled to this card in either direction — neither blocks nor is blocked, and whoever picks them up next need not wait on #10140 or rebase around it.
The file-collision half of that sweep still stands (5 open PRs, zero touching the six files). It was the coupling half that I got wrong.
Two further corrections the dev made to my dispatch are recorded on the PR: the card's "the excerpts omitted the failure REASON" is half true (AssertionError was already a matcher token, so assertions reached the excerpt detached and unlabelled while timeouts produced nothing — that asymmetry is the trap), and I wrongly called three scripts the bot's "gates" when none gated it behaviourally. Full verdict: #10140 (comment)
Activity
claude commentedon Aug 20, 2026
Claim: PM loop round 30 (serial — one baton at a time)
Session:
session_01XqDQYVU5smx29ts9pAErjaBranch:
claude/issue-10128-queue-triage-reason-anchorWorktree:
objectstack-issue-10128Domain:
domain:devxFile surface:
.github/workflows/merge-queue-triage.ymland thescripts/it calls (scripts/pm/ci-failure.mjsis the log-extraction seam;scripts/check-workflow-status-functions.mjs/scripts/pm/check-half-states.mjsare its gates) (stop on breach; explain in the report)Container & model: M, mode:subagent, model: opus —
node scripts/pm/dispatch-gates.mjs --tierrun this round over the sixmerge-queue-triage-bearing paths atorigin/mainc766ec3600: "no path-derived mandate: the surface hits none of the 1 declared glob(s) … This line is a FLOOR, never a clearance." Matches the card's own suggestedM, opus.Clause-②: no — the card draws the boundary itself and I hold it: the bot names and aggregates, decides nothing (no auto-requeue, no auto-quarantine — gate-weakening stays a human act). No contract's accept/reject behaviour moves and no public surface widens. The dev re-declares against its actual diff.
Serial constraints cleared: swept all 5 open PRs — none touches
merge-queue-triage.yml,ci-failure.mjs,check-half-states.mjs,check-workflow-status-functions.mjs, or the cross-repo closer. Two sibling cards named for read-coupling, both still unclaimed inpm:queue, neither in flight: #9898 (ci-failure.mjs's--self-testruns nowhere in CI) and #9966 (ci-failure.mjs's transport probe carries the same false green as #9946). Both land in the same file this card's limb ① extracts from — whoever moves second rebases. Limb ①'s reason-line extraction should not depend on either landing.Generated by Claude Code
feat(ci): merge-queue triage excerpts the failure REASON, and aggrega…
claude commentedon Aug 20, 2026
Dev report for #10128 — structured, machine-parsed by the PM.
{ "issue": 10128, "status": "done", "branch": "claude/issue-10128-queue-triage-reason-anchor", "pr": "https://github.com/objectstack-ai/objectstack/pull/10140", "premise_still_valid": true, "summary": "Clause-②: no. Declared against the actual diff: no spec/Zod schema, no package export, no metadata key and no published-package source is touched; the subject is one internal CI workflow's comment text plus a new repo gate. The bot's accept/reject behaviour is unchanged — it still comments on exactly `merge_group` + `conclusion == failure`. Two things a reviewer could weigh differently and should see explicitly: the job now declares `issues: write` (limb ② files and refreshes an issue), and the bot gains the ability to CREATE issues on the live repo. Neither is a contract accept/reject move by my read, but both are new capability on a shared repo, so if the PM's Clause-② reading covers 'new write capability' rather than 'contract surface', flip it and hold the PR — I will not argue.\n\nIMPLEMENTED. Limb ①: the extractor looks ahead from each FAIL line for the deciding line (Test/Hook/Teardown timed out in Nms | AssertionError | the first `Error:` after it), stops at the next FAIL so no failure borrows its neighbour's reason, and prints it labelled under its FAIL line. Three coupled corrections in the same seam, each with evidence: the three cross marks must now sit at line START (four of twelve lines in the excerpt the bot really published on PR #10008 were `stdout |` noise admitted only by the multiplication sign inside a test TITLE); a line reported as a reason is no longer re-printed as its own excerpt line (12 excerpt lines -> 9 on the real assertion log); excerpt lines are ANSI-stripped. Limb ③: each ejection is keyed by the failing test FILE PATH and records that sighting in its own comment as a machine-readable marker; the next ejection reads 24h of those markers back (bounded, newest-first, 5 pages) and at >= 2 DISTINCT PRs files ONE anchor issue per key or refreshes the open one, matched on a stable per-key body marker, then links it from the victim's comment. BOUNDARY HELD: no requeue, no quarantine, no label on anybody's PR, no skip written anywhere — and it is enforced rather than promised, because the harness records-and-throws on ANY unmodelled API and `judge` fails the scenario on that record, so a future call that requeues or labels fails the battery with no scenario having anticipated it.\n\nLIMB ③ EVIDENCE (all four cases the dispatch asked for, all under doubles). POSITIVE (A1): ledger carries KEY@PR#10105, this run is PR#10008 -> exactly 1 issues.create, 0 issues.update; the created body carries the per-key anchor marker and table rows for #10105 and #10008; the victim comment links the new issue number; the comment carries this run's own sighting marker. REFRESH-NOT-DUPLICATE (A2): driven as a PAIR — run 2's world is built from run 1's ACTUAL output (the issue it created plus the comment it posted), never a hand-written marker, because a hand-written one proves each half while leaving the two runs free to disagree about its spelling. Run 2 is PR#10003: 0 creates, 1 update against the SAME issue number, refreshed body lists #10105, #10008 and #10003, and the third victim's comment links the same anchor. NEGATIVE, different keys (A3): this run keys file A while the ledger holds file B already seen on TWO distinct PRs -> 0 creates, 0 updates, and the comment says the key was seen on this PR alone; the deliberately-poisoned ledger is what makes the aggregation prove it only aggregates keys THIS ejection hit (mutation M6 flips it to aggregate every ledger key and A3 goes red). NEGATIVE, same PR twice (A4): ledger holds the same key on PR#10008 under two different run ids -> 0 creates, 0 updates (mutation M5 counts RUNS instead of DISTINCT PRs and A4 goes red). Bonus (A5): a redelivered workflow_run writes nothing at all and does not even read the ledger.\n\nHOW THE LIVE REPO WAS KEPT OUT OF IT. The gate has no network of any kind: `github` is a Proxy over hand-written stubs, `issues.create` / `issues.update` / `issues.createComment` are functions that append to an in-memory call ledger and return fabricated numbers, and every API the harness does not model is recorded out-of-band and thrown so no scenario can absorb it. Nothing in a gate run resolves a hostname. Corpus capture touched the live repo through READS only (get_job_logs, issue/comment reads) plus one local `vitest run` in my own worktree. The complete list of live WRITES I made this task: the branch push, the draft PR #10140, the additive `skip-changeset` label POST, finding #10141, and the report comment. No test run created an issue or commented on any PR.\n\nSURFACE. Beyond the dispatch's declared surface I added two things, both declared here rather than widened silently: `.github/workflows/lint.yml` (the wiring — a gate nobody runs is this repo's own recorded anti-pattern, and dispatch-gates.mjs now discovers the new family from that step) and `scripts/fixtures/merge-queue-triage/` (the corpus + a README stating each file's provenance). No published package source. `skip-changeset` applied additively (POST /labels, never a wholesale PUT) after reading the labeler bot's `documentation`, `ci/cd`, `size/xl`; read back as [\"documentation\",\"ci/cd\",\"size/xl\",\"skip-changeset\"].\n\nWHERE THIS DISPATCH WAS WRONG, on the record. (1) FALSE: `scripts/pm/ci-failure.mjs` is NOT 'the log-extraction seam limb ① lands in'. The triage bot's log extraction is entirely inline in `.github/workflows/merge-queue-triage.yml`'s github-script block; `ci-failure.mjs` is an unrelated seat-facing CLI whose own header says raw job logs are unreachable from an agent container. Limb ① landed in the workflow and I did not touch `ci-failure.mjs` at all — so #9898 and #9966 are not read-coupled to this card in any direction, and neither blocks nor is blocked. (2) SHARPER THAN THE CARD: 'the excerpts omitted the failure REASON' is only half true, and the half it misses is the mechanism. `AssertionError` was ALREADY a token of the old FAIL matcher, so an assertion DID reach the excerpt — as a detached, unlabelled line — while a timeout produced NOTHING. Measured on the two real captures: BEFORE on the assertion log shows three AssertionError lines, BEFORE on the timeout log shows none. That asymmetry is exactly the trap: a reasonless excerpt reads like the assertion-shaped excerpts a responder has seen before, which is how #10112 got filed. (3) OVERSTATED: the dispatch calls `check-half-states.mjs`, `check-workflow-status-functions.mjs` and `check-cross-repo-closer-outcome.mjs` the bot's 'gates'. None of the three gates it in any behavioural sense — each merely MENTIONS merge-queue-triage in prose (an H12 latency note; a folded-`if:` specimen; a marker-idiom contrast). Before this PR the bot had NO gate at all; that is what `scripts/check-merge-queue-triage-outcome.mjs` now is. (4) Minor and correct-at-the-time: `origin/main` was `c766ec3600` when I re-derived it; it has since moved to `a659896b4d`, with ZERO overlap with my file surface (checked file-by-file), so I left the base alone rather than merging noise into a draft.\n\nCI at report time: 24 check runs on head e4d81f03ca — 12 success, 9 skipped, 3 in_progress (`Lint & Repo Gates`, `TypeScript Type Check`, `Test Core (1/3)`), ZERO red. Reporting now per the draft-PR-time contract; `Lint & Repo Gates` is the job that runs the new gate and had not converged.", "tests": "All at head e4d81f03ca (the final commit; the union below was run on that tree). Exit codes captured before any pipe.\n\nnode scripts/check-merge-queue-triage-outcome.mjs --self-test -> EXIT 0: \"✓ check-merge-queue-triage-outcome --self-test: 78 assertions, 15 mutations of the shipped script each driven to red.\"\nnode scripts/check-merge-queue-triage-outcome.mjs -> EXIT 0: \"check-merge-queue-triage-outcome: OK (61 assertions over 18 scenarios, driving the 21135-char script extracted from .github/workflows/merge-queue-triage.yml against real captured logs).\"\n\nLIMB ① BEFORE/AFTER, verbatim, both directions, on real captured logs of the incident's own file (packages/plugins/plugin-dev/src/dev-plugin-security-enforcement-warning.test.ts at 7552e03375, unbuilt closure — the exact condition #10112 measured). BEFORE = the script extracted from origin/main; AFTER = the script extracted from HEAD; same doubles, same fixture bytes.\n TIMEOUT, BEFORE: 'FAIL src/dev-plugin-security-enforcement-warning.test.ts > ... > bail #1 (no objectql/metadata service): the three' (and no reason line anywhere — 8 excerpt lines, none naming why)\n TIMEOUT, AFTER : 'FAIL src/dev-plugin-security-enforcement-warning.test.ts > ... > bail #1 (no objectql/metadata service): the three'\n '↳ 失败原因: Error: Test timed out in 1ms.'\n ASSERTION, BEFORE: 'FAIL src/dev-plugin-security-enforcement-warning.test.ts > ... > bail #1 (no objectql/metadata service): the three'\n 'AssertionError: SecurityPlugin.init() ran: expected false to be true // Object.is equality' (present, but DETACHED and UNLABELLED — see correction 2)\n ASSERTION, AFTER : 'FAIL src/dev-plugin-security-enforcement-warning.test.ts > ... > bail #1 (no objectql/metadata service): the three'\n '↳ 失败原因: AssertionError: SecurityPlugin.init() ran: expected false to be true // Object.is equality' (9 lines, was 12 — the duplicate is gone)\n The two FAIL lines above are BYTE-IDENTICAL across the two logs; scenario E3 asserts that identity rather than describing it, so the corpus cannot drift into two obviously-different logs and leave the claim unproven.\n THIRD FIXTURE, the excerpt the bot really published on PR #10008 for queue build 32333709633, read back through the API: BEFORE = 7 lines, four of them `stdout |` noise and raw ANSI, no reason anywhere; AFTER = 3 FAIL lines, noise gone, each carrying '↳ 失败原因: (这条 FAIL 之后 12 行内没有可识别的原因行)'. That last line is the measured BEFORE stated honestly: the reason was never in what the reader saw.\n\nANTI-NO-OP LIMB, inside the bot itself (not only in the gate): unreadable logs / zero derivable keys emit a core.warning titled 'Queue-signature aggregation had nothing to key on' and the comment says 这一轮没测到 (an absent MEASUREMENT, not an absent signal) — scenario A6; a truncated 24h ledger warns and reports its counts as 下界 (lower bounds) — A7; an anchor whose absence could not be established is NOT created — A8. Mutations M9/M10 downgrade the first two warnings to info and A6/A7 go red.\n\nRepo gates, derived from the ACTUAL changeset with `node scripts/pm/dispatch-gates.mjs` (no paths passed; it took the 7-path change set from the merge base itself) and run after the final commit:\n pnpm check:cross-package-test-inputs EXIT 0 'All 57 self-test cases passed. / OK: 12 package(s) read outside themselves, all declared'\n pnpm check:node-version EXIT 0 'OK (30 setup-node step(s) across 27 workflow(s), all on Node 22)'\n pnpm check:required-contexts EXIT 0\n pnpm check:shard-attestation EXIT 0 '92 assertions ... 2 aggregate gate(s) count 3 declared leg(s)'\n pnpm check:type-check-coverage EXIT 0 'OK — 64/77 workspace packages type-checked'\n pnpm check:workflow-status-functions EXIT 0 '27 workflow file(s), 46 job(s), 24 job-level if: expression(s)'\n pnpm check:nul-bytes EXIT 0 '6380 text file(s) ... no raw ASCII control bytes'\n node scripts/check-cross-repo-closer-outcome.mjs (+ --self-test) EXIT 0 (adjacent bot; lint.yml is touched, and its self-test pins that wiring)\nDECLARED NARROWING: `pnpm check:type-check-debt` was NOT run locally. It refuses to re-measure without a full built package closure ('55 workspace dependenc(ies) ... have no built type entry point on disk') and this diff contains ZERO TypeScript and no package.json (verified: `git diff --name-only origin/main...HEAD | grep -cE '\\.(ts|tsx|mts|cts)$'` = 0, package.json = 0). It appears in the derived list only because .github/workflows/lint.yml is touched. Building the whole package graph to re-prove an unrelated ledger is not a cost this card should spend on a shared container; CI runs it on a built tree.\n\nByte discipline: the fixtures store ESC in its escape spelling (a raw 0x1b is a check:nul-bytes failure) and the harness materialises it; the workflow builds ESC with String.fromCharCode(27) rather than typing an escape into YAML. `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` over every touched file: no match. Worth recording that this bit me once mid-task — an ESC materialised into a heredoc I was writing for the fixtures README, and the tool layer refused the command; the README is now generated with the escape spelling constructed rather than typed.", "open_questions": [], "out_of_scope_findings": [ "filed as #10141: raw CI job logs ARE reachable from an agent seat via the GitHub MCP server's get_job_logs (tail window), while direct egress to the log archive is still refused with CONNECT 403 — so ci-failure.mjs's header claim that raw logs are 'NOT a foundation any seat-facing tool can stand on' is true only of direct egress. Both halves measured today. Labelled `finding`, unassigned, adjacent to #9898/#9966." ] }Generated by Claude Code
claude commentedon Aug 20, 2026
Correction to my claim comment above — posting it here rather than editing in place, so the wrong version stays readable.
My
Serial constraints cleared:line named #9898 and #9966 as read-coupled to this card, on the premise thatscripts/pm/ci-failure.mjsis "the log-extraction seam limb ① lands in". That premise is false. The triage bot's log extraction is entirely inline in.github/workflows/merge-queue-triage.yml's github-script block;ci-failure.mjsis an unrelated seat-facing CLI and PR #10140 does not touch it. #9898 and #9966 are not coupled to this card in either direction — neither blocks nor is blocked, and whoever picks them up next need not wait on #10140 or rebase around it.The file-collision half of that sweep still stands (5 open PRs, zero touching the six files). It was the coupling half that I got wrong.
Two further corrections the dev made to my dispatch are recorded on the PR: the card's "the excerpts omitted the failure REASON" is half true (
AssertionErrorwas already a matcher token, so assertions reached the excerpt detached and unlabelled while timeouts produced nothing — that asymmetry is the trap), and I wrongly called three scripts the bot's "gates" when none gated it behaviourally. Full verdict: #10140 (comment)Generated by Claude Code
merge-queue triage: excerpt the failure REASON, and aggregate same-si…