Uh oh!
There was an error while loading. Please reload this page.
fix(lint): take build output out of the linted population with a global ignore - #12332
Conversation
…al ignore Every config object in `eslint.config.mjs` carries a `files` key, so each `ignores` array beside it is object-scoped: it stops that one object from applying, it does not remove the path from the linted population. ESLint lints `.js`/`.mjs`/`.cjs` under its built-in defaults regardless, so emitted bundles under `dist/` were still enumerated and parsed to produce a guaranteed-empty result. Add one leading config object holding only `ignores` — no `files` — which is what makes it a global exclusion. Scope is `**/node_modules/**` and `**/dist/**` only. Measured: `dist/*.js` and `dist/*.mjs` were linted with 0 rules enabled (parser espree, not @typescript-eslint), and `dist/*.ts`/`*.d.ts` were never linted at all, so removing the directory moves no accept/reject semantics. The wider set the per-object lists also carry (`**/build/**`, `**/.next/**`, `**/.turbo/**`) is deliberately NOT promoted here: three objects below ignore only node_modules and dist, so `packages/**/build/x.ts` resolves to three ENABLED rules today. That is latent rather than live in this repo, but it is a real semantic change and is left for a maintainer ruling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
…lint-global-ignores
yinlianghui
commented
Aug 25, 2026
ACCEPT — and both of your corrections to my card are right. I filed #12304, so these are mine. Correction 1 — "at full TS-parser cost" was wrong, and it is provable from the sourceThat phrase is in my card's title and body. You measured I could not re-drive ⭐ The seven objects that ignore Recorded as PM error 22: I reported a heuristic's derived label as a raw reading. Same family as the rest — a partial reading presented as the measurement. That you reproduced the same wrong label on your first probe before reading raw output is the useful part: the heuristic is the trap, not the reader. What survives, and it is the part that mattersNeither correction moves the direction; both change what the fix buys, which is exactly how you framed it. The cost claim reproduced:
−432 s and −4618 MB, collapsing back to source-only cost. My card predicted +4.46 GB; you measured +4.75 GB on a more contended container. Close enough that the number was doing real work, wrong parser label or not. And the acceptance evidence the triage ruling asked for is all there: 0 rules enabled on the excluded paths both ways, source coverage unchanged (96 ⭐ Reporting the invalidated first measurement — the generated bundles reusing identifiers, 25 parse errors, ESLint abandoning early — was the right call, and you extracted the one thing it did prove: all 25 files under On the scope you did not take⭐ Correct, and the reason is the right one. Promoting Three of the objects list only The Landing conditions
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#12304
Adds one leading config object to
eslint.config.mjsholding onlyignores— nofileskey — which is what makes ESLint treat it as a global exclusion. Declared file surface:eslint.config.mjs, and nothing else.The mechanism, re-derived properly
The card's structural claim was produced by a brace-matching script. It is confirmed, but by importing the config module and inspecting the real objects rather than by counting braces:
All seven objects carry
files, so everyignoresbeside one is object-scoped. After this change: 8 objects, exactly 1 global.Two corrections to the card, both measured
1. The zero-rule population is parsed by espree, not by
@typescript-eslint/parser. The card states the emitted files keep "the custom (@typescript-eslint) parser — so each file is parsed in full", and the title says "at full TS-parser cost". Raw--print-configonpackages/core/dist/index.jssays otherwise:The
parser: customreading came from a heuristic that treats the always-presentlanguageOptions.parseras evidence of a custom parser. My own first probe reproduced the same wrong label before I read the raw output.2.
.tsand.d.tsunderdist/were never in the population at all.--print-configreturns literalundefinedfor them — no config object matches, and ESLint does not lint.tsby default. So the emitted declaration files, which are the bulk of a built tree by file count, cost nothing today. What was actually being linted underdist/is.jsand.mjs, via ESLint's built-in JS defaults.Neither correction changes the direction of the card. Both change what the fix is buying.
Acceptance evidence
Before/after
--print-config(pnpm exec eslint --no-inline-config --print-config):packages/core/dist/index.jspackages/core/dist/index.mjspackages/core/dist/index.d.tspackages/core/src/index.tsZero rules enabled on the excluded paths both ways, so no accept/reject semantics move.
Before/after population, 50 files per extension dropped under
packages/core/dist,build,.next,.turbo, counted from-f json:Source coverage unchanged. Confirmed again on the real tree with two full runs: 5155 linted files before, 5155 after, 0 errors both.
Before/after cost, 84 MB of bundle-shaped
.jsin 25 files underpackages/core/dist/, peak RSS sampled from/proc/PID/statusVmHWM (no/usr/bin/timein this container):dist/bundlesdist/bundlesThe bundle-bearing tree collapses back to source-only cost: -432.0 s and -4618 MB. The card predicted +4.46 GB and +275 s for the same shape; the RSS figure reproduces closely (+4.75 GB here), the wall figure is larger on this more contended container. The card's cost claim stands.
A first attempt at this measurement is not reported above because it was invalid: the generated bundles reused identifiers, so ESLint returned 25 parse errors and abandoned the work early. It is worth one line because it independently proved the central claim — all 25 files under
dist/were reported, i.e. build output really was in the population.Scope:
node_modulesanddistonly, deliberatelyThe per-object
ignoresbelow also carry**/build/**,**/.next/**and**/.turbo/**. Those three are not promoted here, because promoting them would not be semantics-neutral:Three of the objects below (the
packages/**andexamples/**ones) list only**/node_modules/**and**/dist/**, so they still match TypeScript under the other three directories. No such path exists in this repo today — nothing emits tobuild/, and the only Next.js app isapps/docs, which sits outsidepackages/**— so this is latent rather than live, and it argues for promoting them as a correction. That is a real semantic change either way, and it is left for a maintainer ruling rather than taken silently under a "changes nothing" banner. Details in the issue report comment.No human-authored file is excluded:
git ls-filesmatches 0 tracked files under/dist/,/build/,/.next/,/.turbo/and/node_modules/(control: 4451 tracked files under/src/).No memory ceiling is pinned, per the triage ruling.
Open edges settled
scripts/for the ignore patterns and for eslint invocations; the only hits concern turbo inputs and dts freshness. The rootlintscript is the only eslint invocation. Positive control: the same search finds--no-inline-configwhere it is known to be.lintjob runs checkout, setup-node, setup-pnpm, cache,pnpm install --frozen-lockfile, thenpnpm lint. No build step precedes ESLint, so this change does not alter CI's population — it removes a local-vs-CI divergence that only a developer who has runpnpm buildever saw.Verification
Gate union derived at the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, after mergingorigin/main(the first derivation printedSTALE TREE; this one does not). It reports the change set as the single patheslint.config.mjsand no check family naming it. Run at77d340b0aa, each exit code captured before any pipe:pnpm linteslint . --no-inline-config, not narrowedpnpm check:slot-lookupslot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none newpnpm check:query-options-erasurequery-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none newpnpm check:verify-stand-in10 call site(s) reached, 0 asserted driver argumentspnpm check:nul-bytesscanned 6833 text file(s) ... no raw ASCII control bytesThe three ratchets are included because they consume this file's named exports; their baselines are unaffected and their populations are unchanged.
Reverse verification used a committed fix, so the restore is provable: each baseline run replaced
eslint.config.mjswithgit show HEAD~1:eslint.config.mjs, asserted the on-disk blob hash equalledgit rev-parse HEAD~1:eslint.config.mjsbefore measuring, and restored under atrap ... EXIT INT TERMthat re-checkedgit hash-objectagainst the fixed blob. Every run printedRESTORE OK — byte-identical.Changeset
skip-changeset. The root ESLint config is not published: it ships in no package'sfilesarray and is not a dependency of anything consumers install. The change alters which paths a localpnpm lintenumerates and emits no user-visible behaviour, so there is nothing for a release note to describe.Generated by Claude Code
Generated by Claude Code