Repository navigation
Merge-queue triage guidance: the AssertionError vs timeout heuristic has a counterexample class — the discriminator is what the assertion MEASURES, not what kind of failure it is #13830
Copy link
Copy link
Closed
Labels
documentationImprovements or additions to documentationImprovements or additions to documentationdomain:skillspriority:p3
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentation
on Aug 31, 2026 os-dev-report
{ "issue": 13830, "status": "done", "branch": "claude/issue-13830-queue-triage-discriminator", "pr": "https://github.com/objectstack-ai/objectstack/pull/13966", "premise_still_valid": true, "summary": "Carrier grep on origin/main resolves the 超时/断言 pair to EXACTLY ONE carrier: .github/workflows/merge-queue-triage.yml (the two body-array lines that compose the posted triage comment). landing-operations.md, dispatch-runbook.md (the HELD file — so there is no would-be edit to record against PR #13948), and pm-dispatch/SKILL.md carry no copy, so neither stop condition fires; check-merge-queue-triage-outcome.mjs and the merge-queue fixtures name AssertionError as parser input and fixture bytes, not as guidance prose. Added the named exception in two places in that one file: one paragraph in the posted comment body, and a provenance section in the YAML header. The discriminator is what the assertion MEASURES — product behaviour (a value, a shape, a refusal) reads as a real behaviour change and is never re-queued away; a precondition of the experiment's own validity (elapsed time, ordering under load, anything holding only inside a time budget, the tell being that the message or the compared values name a duration/timestamp/elapsed count) reads WITH the timeouts, where one re-queue is a legitimate discriminator. The diff is 46 insertions, 0 deletions: the existing heuristic lines are byte-identical to origin/main, so the rule is named-around, not softened, and the re-queue allowance is untouched (stated as a boundary in both new passages). The card's measured pair is cited with issue numbers in the YAML header, this file's existing idiom for provenance; the posted body carries the two cases descriptively, as measured bytes, because that body is auto-posted on every red queue build and bare refs in it would mint a fresh cross-reference on both cards each time. No id-lint gate is in force on .github/workflows (check:pm-skill-id-lint scopes to .claude/skills/pm-dispatch), so that is a judgment on comment noise, declared in the PR body and reversible in one line. PR is draft; skip-changeset applied at open time (the diff publishes nothing) and confirmed surviving the size-labeler's whole-set write by comparison read-back.", "tests": "All on the final head 0183098e (= the PR head; nothing landed after the gate run). Gate union derived on that head with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` — the script's own change-set derivation, no hand-built path list; stderr confirmed the answer came from objectstack-ai/objectstack at 0183098e and that --repo held against the checkout's origin. It named 19 families. Ran all 19, plus the pin suite's --self-test leg (lint.yml invokes both) and the always-run check:nul-bytes = 21 invocations, 21 PASS, 0 red. Exit codes captured before any pipe (redirect to a file, then read it); verdicts quoted are the lines the gates themselves printed. Load-bearing ones: 'check-merge-queue-triage-outcome --self-test: 89 assertions, 17 mutations of the shipped script each driven to red' and '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)' — the char count is the proof the edited YAML still parses and the comment body still composes; also 'check-workflow-status-functions: OK (scanned 29 workflow file(s), 52 job(s))' and 'check-nul-bytes: OK (scanned 7660 text file(s) ... no raw ASCII control bytes)'. Edits were confirmed on disk by grep -c on both injected anchors (1 and 1) before any measurement, and by git diff --stat reading 46 insertions / 0 deletions. No ablation was run: the change is static prose in a comment body, with no code path to mutate. DECLARED NARROWING — pnpm lint (repo-wide eslint) was narrowed to the diff's paths, and the narrowing is a measurement, not a skip, on three readings: (1) population read from eslint.config.mjs itself — every files: glob is ts/tsx/mts/cts/js/jsx/mjs/cjs, none names .yml; (2) count read from --format json — the one path returns a single result entry whose only message is 'File ignored because no matching configuration was supplied', errorCount 0, so the diff contributes zero linted files; (3) invariance for untouched files — no parserOptions.project and no projectService anywhere in the config (it states this at line 328), so with no cross-file type program a .yml edit cannot move any untouched file's verdict, and the diff edits no eslint config. CI runs the full farm anyway.", "mcp_calls": "1 — the claim comment on 13830. Everything else went zero-quota or core-bucket: issue body and comments off the public-repo page payload, all listing/dedup/PR-create/label/issue-create/read-back over repo-scoped REST (probed 200 in this container first), and the carrier grep over git.", "open_questions": [], "out_of_scope_findings": [ "filed as #13967: the merge-queue triage comment's operative guidance prose is unpinned — check-merge-queue-triage-outcome.mjs pins extraction and cross-PR aggregation but reads the fenced excerpt only (by design, per excerptLines' own docblock), so both guidance lines could be deleted with the whole 19-family union staying green. Observation-class, labelled `finding`, unassigned; deliberately NOT fixed in this PR — different defect class from this card, and choosing which claims are load-bearing enough to pin is the judgment the card is for." ] }
Generated by Claude Code
Metadata
Metadata
Assignees
Labels
documentationImprovements or additions to documentationImprovements or additions to documentationdomain:skillspriority:p3
Split out of #13691 by the triage seat. That card is one timing guard in
driver-sql; this is the reusable correction its author flagged as "worth more than this single test" — and which, filed nowhere, would have closed along with it. Prose in a comment is invisible to candidate queries, sweeps and ageing alarms; this card is the record.The standing guidance
The merge-queue triage comment instructs readers, verbatim:
That heuristic is sound in general and is doing real work — the
domain:devxseat used it the same day to refuse a flake reading on #13683 (anAssertionErroronrun-dev-unbuilt-workspace.e2e.test.ts), correctly, and that refusal is why that card exists.The counterexample
#13691 is an
AssertionError:By the heuristic this reads as "real behaviour change". It is not. The assertion is a validity precondition — the test requires its own run to fit inside one second so that a truncated second-precision stamp could not have distinguished the updates, which is what gives the following distinctness assertion its meaning. The real invariant (monotonicity, asserted on the line above) held.
⇒ The experiment was invalidated. The product was not falsified. And it was confirmed load-dependent by evidence: same head, same test, re-queued once with no change → success.
The correction
The discriminator is not
AssertionErrorvs timeout. It is what the assertion measures.A validity-precondition assertion is a timing measurement wearing an
AssertionError's clothes.Why it is worth encoding rather than remembering
⭐ The tell is cheap and mechanical: the assertion's message or its compared values name a duration, a timestamp, a count of elapsed units, or an ordering that only holds under a time budget.
expected 1006 to be less than 1000, with a message beginning "this run took over a second", is unambiguous on its face.Scope
One paragraph in the merge-queue triage guidance (and, if the same wording is carried in the dispatch playbook, there too — grep for the 超时/断言 pair before editing, so the two copies do not diverge).
run-dev-unbuilt-workspace.e2e.test.tsfails in the merge queue: the raw MODULE_NOT_FOUND reaches stderr instead of the CLI's diagnostic — and the module that fails to resolve is the formatter's own import #13683 the same morning. This adds a named exception; it does not soften the rule..claude/**) ⇒ human-merge only, ⛔ never armed, ⛔ never queued.Refs
sql-driver-11224-update-stamp-precision§3 now reds on a SLOW runner too — thespan < 1000msline is a validity precondition, and #11572's fix spends deliberate clock time inside that same budget #13691 — the counterexample, with the mechanism and the confirming re-queue (its author flagged this as the transferable half).run-dev-unbuilt-workspace.e2e.test.tsfails in the merge queue: the raw MODULE_NOT_FOUND reaches stderr instead of the CLI's diagnostic — and the module that fails to resolve is the formatter's own import #13683 — the same morning's other queue failure, where the heuristic worked correctly. Both are worth citing in the guidance: the pair is what makes the distinction legible.sql-driver-11224-update-stamp-precision§3 is a probabilistic guard that reds with CORRECT code on a fast runner — and its failure is byte-identical to the defect it guards against #11572 / PR test(driver-sql): make the update-stamp precision guard deterministic — drive §3's clock instead of racing it #12112 — why [finding]sql-driver-11224-update-stamp-precision§3 now reds on a SLOW runner too — thespan < 1000msline is a validity precondition, and #11572's fix spends deliberate clock time inside that same budget #13691's budget is tight (the fast-runner fix spends deliberate clock time inside the same 1000 ms wall).