Uh oh!
There was an error while loading. Please reload this page.
fix(scripts): key an i18n-coverage cause on its conclusion, not the per-config rerun command - #11547
Merged
Merged
Conversation
…er-config rerun command `groupFailuresByCause` keys a cause on `[reason, fix]`, which holds only while `fix` is a shared remedy. Three of the five failure branches set it to `rerunFix(configPath)` — a string embedding the config path — so every config reaching one of them got a unique key and one shared cause was split into one group per config: the "one cause reported as N results" shape the grouping was written to prevent. Measured on an unbuilt worktree with the CLI probe deferring: all twelve configs fail for one environment fact and the report read `12 of 12 config(s) failed to lint (12 distinct causes)` — twelve blocks, twelve identical `why:` lines — while its own closing paragraph asserted a cause shared by several configs is stated once. Now `1 distinct cause`, over the same twelve failures. Fixed at the identity: no classifier receives a config path any more, so a per-config string is not in reach of a cause, and the rerun command is rendered per config inside the block as the remedy detail it is. The non-JSON branch had the same defect through the other half of the key (byte count and parser offset interpolated into `reason`); those are readings and moved to `evidence`. The classified branches (`pnpm build` / `pnpm install && pnpm build`) group byte-for-byte as before, pinned. The two in-loop verdicts are now pure constructors so `--self-test` can drive them — CI builds the workspace before this gate runs and never reaches these branches. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
os-steve
marked this pull request as ready for review
August 24, 2026 03:21
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#11395
groupFailuresByCause()keys a cause on its conclusion —[reason, fix]— whichholds only while
fixis a shared remedy. It was not. Three of the five failurebranches set
fixtorerunFix(configPath), a string embedding the config path, soevery config reaching one of them got a unique key and one shared cause was split
into one group per config: exactly the "one cause must not be reported as N results"
shape (#5217) that this grouping was written to prevent.
Measured, not reasoned
The defect is only visible from a specific environment state, so that state was
reproduced rather than argued from the regex: an unbuilt worktree (no
node_modules, nodist/) with the gate invoked from a cwd where its CLI probedefers, so all twelve configs land in the fallback branch instead of a classified
one. Both runs are over the same twelve failures — the same tree, the same cwd,
nothing built in between — and the numbers are parsed out of the report, not read
off the prose.
cd932772)f28c758f)12 of 12 config(s) failed to lint (12 distinct causes)12 of 12 config(s) failed to lint (1 distinct cause)Cause N of Mblockswhy:linesfix:linesThe report's own closing paragraph — "a cause shared by several configs is stated
once, not once per config (#5217)" — was the thing contradicted, and it is left
exactly as written. The prose was right; the code was wrong.
What was fixed, and where
Fixed at the identity, not by normalising the config path back out of the key:
that would make the key a function of a string the fix text happens to embed, and
the next
rerunFix-shaped helper would re-open the same hole silently.explainConfigFailure()tookconfigPathfor exactly one purpose — the rerun command — so the parameter isgone. A per-config string is no longer in reach of a cause.
reportUnmeasuredConfigs(), as the remedy detail it is. All twelve commands arestill there for the reader; they are simply no longer part of an identity.
fixis drawn from a closed set of three constants (SHARED_REMEDIES).pnpm build/pnpm install && pnpm build) groupbyte-for-byte as before, pinned by a new assertion on the literal remedy text.
The other half of the key — named because the card's claim needed checking
The card says the same bug applies to the two other
rerunFixcall sites. Verified:there are exactly three call sites (fallback, empty output, non-JSON) — no fourth
has appeared. But fixing
fixalone would not have closed the non-JSON one: itsreasoninterpolatedraw.lengthand the parser's message, so two configs whosenon-JSON output differed by a byte were still one cause reported as two, through the
other half of the key. Those are readings, so they moved to
evidence, which isnot keyed. This is the same defect class, in a branch the card names, in the same
file and the same gate family — fixed here rather than filed, and called out here so
it is reviewed rather than absorbed.
The two in-loop verdicts are now pure constructors (
emptyOutputFailure,nonJsonOutputFailure) so--self-testcan drive them at all: CI builds theworkspace before this gate runs and never reaches these branches, which is why the
split survived.
Non-vacuity
New
--self-testassertions are written as a property over every failure branch(five of them), not one example — the defect arrived through two different halves of
the key, and a test against either half alone passes while the other stays broken.
Each branch is driven with three configs failing the same way; the converse is
pinned too (six genuinely distinct causes must stay six, so "collapse everything"
cannot satisfy the suite).
Ablation, two legs, each mutation confirmed on disk by
git hash-objectand ananchored marker count, each restored byte-identical, all wrapped in
trap restore EXIT INT TERM:fixon all three branches(
0c157bd8→7fa77558): self-test red, 11 failures.reason, non-JSON branch only(
0c157bd8→e888b6b5): self-test red, 2 failures — proving the second halfof the key is independently guarded.
0c157bd8, byte-identical to the committed blob.Leg 1 produced 11 red, not the 12 predicted: on the non-JSON branch that leg injected
${raw.length}rather than a path, so…with no config path in the cause's identitycorrectly stayed green while the other three assertions on that branch went red. The
prediction was wrong about that one assertion and the deviation is recorded rather
than smoothed over — it shows the four assertions are independent, not redundant.
Verification
All at
f28c758f, exit codes captured before any pipe:pnpm build—VERDICT command-exit 0(71/71 tasks)pnpm check:i18n-coverageover the built tree —OK (12 config(s), 602 baselined untranslated string(s), none new); the green path and the ratchet areuntouched
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, 7 families) — all exit 0:check:cross-package-test-inputs,check:entry-guard,check:parse-guard,check:pnpm-filter-targets,check-ci-filter-parity.mjs,check-cross-package-test-inputs.mjs,check:i18n-coveragepnpm check:nul-bytes—OK (scanned 6460 text file(s) … no raw ASCII control bytes)pnpm lint— the whole repo,eslint . --no-inline-config, exit 0, no output.No narrowing was needed, so none is claimed.
No changeset: the root package is private and
scripts/is in no workspace package,so this PR publishes nothing —
skip-changeset.Related
#5217 (the rule this branch escaped) · #10907 (where it was measured) · #11394
already covers the deferring CLI probe that makes this state reachable — the
instrument here, filed separately, not touched by this PR.
Generated by Claude Code
Generated by Claude Code