Skip to content

Merge-queue triage: name the exception — the discriminator is what the assertion measures - #13966

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-13830-queue-triage-discriminator
Aug 31, 2026
Merged

Merge-queue triage: name the exception — the discriminator is what the assertion measures#13966
os-sam merged 1 commit into
mainfrom
claude/issue-13830-queue-triage-discriminator

Conversation

@claude

@claude claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #13830

The merge-queue triage comment tells its reader that a timeout is load/timing and an AssertionError points at a real behaviour change. That rule is right about the common case and is doing real work — the same morning the counterexample below was measured, a seat used it to refuse a flake reading on an AssertionError (#13683), correctly, and that refusal is why that card exists.

But "assertion ⇒ real behaviour change" has a counterexample class, and it is the expensive direction. #13691 ejected the queue with an AssertionError that measures nothing about the product:

AssertionError: this run took over a second, so second-precision stamps could have
differed too: expected 1006 to be less than 1000

That is the test's own validity precondition — the run has to fit inside one second so that a truncated second-precision stamp could not have distinguished the two updates, which is what gives the distinctness assertion below it any meaning. The invariant actually under test (monotonicity, asserted a line above) held. The experiment was invalidated; the product was not falsified — and it was confirmed load-dependent by evidence rather than taste: same head, same test, re-queued once with no change, success.

Under the rule as written, the instructed outcome there is to send an author to debug a product that is not broken, on a red they cannot reproduce.

What this changes

The discriminator is not AssertionError vs timeout. It is what the assertion MEASURES.

the assertion measures reads as
product behaviour — a value, a shape, a refusal real behaviour change: investigate, never re-queue away
a precondition of the experiment's OWN validity — elapsed time, ordering under load, anything holding only inside a time budget with the timeouts: load-sensitive, and one re-queue is a legitimate discriminator

The tell is cheap and mechanical: the assertion's message or its compared values name a duration, a timestamp, or a count of elapsed units.

  • ⛔ This names an exception; it does not soften the rule. The diff is 46 insertions(+), 0 deletions(-) — purely additive, and the two existing guidance lines are byte-identical to origin/main.
  • ⛔ It changes only how a red is read. Which tests may be re-queued is governed elsewhere and is untouched — stated as a boundary in both the posted paragraph and the header section.

Carrier grep — the result is one carrier, not two

The card and the claim both asked for the 超时/断言 pair to be located by grep before editing, so the copies cannot diverge. Grepped against origin/main, the pair resolves to exactly one carrier:

  • .github/workflows/merge-queue-triage.yml — the two body array lines that compose the posted triage comment.

Checked and not carriers (no copy of the pair, so nothing to keep in sync):

candidate reading
.claude/skills/pm-dispatch/references/landing-operations.md no match for 超时 / 断言 / 负载 / 时序 / 行为改变, and no queue-red-reading prose at all
.claude/skills/pm-dispatch/references/dispatch-runbook.md — the file HELD by pending PR #13948 no copy lives there, so there is no would-be edit to record against the hold. Its 分诊 hits are triage-round scheduling, an unrelated sense of the word
.claude/skills/pm-dispatch/SKILL.md no copy. The fable-mandated-face stop condition does not fire
scripts/check-merge-queue-triage-outcome.mjs, scripts/fixtures/merge-queue-triage/** these name AssertionError as parser input and fixture bytes, not as guidance prose

Copy count 1, so neither of the claim's stop conditions (count above two · SKILL.md a carrier) fires.

Why the numbered pair is in the header and not in the posted body

The card asks for the counterexample and its same-morning control to be cited, because the pair is what makes the distinction legible. They are cited, in full, in the file-header section — which is this file's existing idiom for provenance (#4859, #10112, #10120, #10115, #10128 all live there and none of them is in the posted body).

Keeping the two numbers out of the posted body is deliberate and mechanical, not a softening: that body is auto-posted onto a PR on every red queue build, and a bare issue reference in it would mint a fresh cross-reference event on both cards every single time. The body therefore carries the two cases descriptively — and it carries them as measured bytes, which is the operative half anyway: the precondition assertion verbatim from the counterexample, against this file's own already-quoted product-behaviour assertion (SecurityPlugin.init() ran: expected false to be true). No id-lint gate is in force here — check:pm-skill-id-lint scopes itself to .claude/skills/pm-dispatch — so this is a judgment about comment noise, and it is reversible in one line if the seat prefers the numbers inline.

Verification

All on the final head 0183098e, gate union derived on that same head with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (the script's own change-set derivation, no hand-built path list).

19 derived families + the pin suite's --self-test leg + check:nul-bytes = 21 invocations, 21 green, 0 red. Exit codes captured before any pipe (redirect, then read the file), and the verdicts below are the lines the gates themselves printed:

✓ check-merge-queue-triage-outcome --self-test: 89 assertions, 17 mutations of the
  shipped script each driven to red.
check-merge-queue-triage-outcome: OK (73 assertions over 20 scenarios, driving the
  28340-char script extracted from .github/workflows/merge-queue-triage.yml against
  real captured logs).
check-workflow-status-functions: OK (scanned 29 workflow file(s), 52 job(s), ...)
check-nul-bytes: OK (scanned 7660 text file(s) ... no raw ASCII control bytes).
GATE UNION VERDICT: 0 failing famil(ies) of 21 invocations

The pin suite is the load-bearing one for a workflow edit, and it ran on both legs the way lint.yml invokes it. Its plain run reports the char count of the script it extracted from the edited YAML, so the edit is parse-clean and the comment body still composes; its --self-test drove 17 mutations of that shipped script to red, so the battery is not decoration.

ESLint: a declared narrowing, and a measured one

pnpm lint is a repo-wide scan CI owns. It was narrowed to the diff's paths, and the narrowing excluded nothing — three readings, none of them a guess:

  1. Population, read from eslint's own config rather than from an assumption about what counts: every files: glob in eslint.config.mjs is **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} or a narrower ts/tsx/mts/cts subset. No glob names .yml.
  2. Count, read from --format json: running eslint --no-inline-config --format json over this diff's one path returns exactly one result entry whose only message is File ignored because no matching configuration was supplied.errorCount: 0, and zero rules applied. This diff contributes zero linted files.
  3. Invariance for untouched files: the config runs no type-aware linting — no parserOptions.project, no projectService, every parserOptions in the file is a bare { ecmaVersion, sourceType }, and the config states this at line 328. With no cross-file type program, a .yml edit cannot move the verdict on any file it did not touch. The diff also edits no eslint config.

Changeset

None, deliberately — the diff is one .github/workflows/** file and publishes nothing from any package. skip-changeset applied at PR-open time.


⚠️ Draft, and it stays draft for this seat's review. The diff is a pure workflow-text change (no .claude/** file is touched), so it follows the normal code path, but the dispatch holds it as a draft either way.


Generated by Claude Code

…hat the assertion MEASURES

The posted triage comment tells readers that a timeout is load/timing and an
`AssertionError` points at a real behaviour change. That rule is right about
the common case and demonstrably prevented a wrong flake call the same morning
this exception was measured — but "assertion ⇒ real change" has a
counterexample class, and it is the expensive direction.

A validity-precondition assertion (the run must fit inside a time budget for
the following distinctness assertion to mean anything) measures nothing about
the product. It reads with the timeouts: load-sensitive, and one re-queue is a
legitimate discriminator. The tell is mechanical — the assertion's message or
its compared values name a duration, a timestamp, or a count of elapsed units.

Purely additive (46 lines, 0 deletions): one paragraph in the posted comment
and the provenance section in the file header. The existing heuristic is
untouched, and so is the re-queue allowance, which is governed elsewhere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants