Observation
eslint.config.mjs lists **/dist/**, **/build/**, **/.next/**, **/.turbo/** in the ignores of every config object — but each of those objects also carries a files key. In ESLint flat config an ignores alongside files is object-scoped, not a global ignore: it stops that object from applying, it does not remove the file from the linted population. So build output stays in the sweep.
Measured on 8649b398b8, Linux, Node 22.22.2 (the pinned toolchain: .nvmrc = 22, engines.node = >=22.0.0, lint.yml uses setup-node with node-version: '22'):
1. Build output is in the population. Dropping 20,000 synthetic .js files under packages/<pkg>/dist/ took the linted population from 5,142 → 25,142 files. Every one of them was linted.
2. Those files can never produce a finding.eslint --print-config on a dist/*.js:
rules_configured: 0 rules_ENABLED: 0 parser: custom
Zero rules, and still the custom (@typescript-eslint) parser — so each file is parsed in full to produce a guaranteed-empty result.
3. The cost is not small. 83 MB of emitted-bundle-shaped .js (25 files) placed under a dist/ directory, against the same source tree:
| tree | linted files | peak RSS | wall |
|---|
| source only | 5,142 | 676.9 MB | 71.3 s |
+ 83 MB of dist/ bundles | 5,167 | 5137.1 MB | 346.5 s |
+4.46 GB of peak RSS and +275 s of wall time, for zero reachable lint findings.
Why it matters
Suggested direction (not verified)
Promote the build-output patterns to a global ignore — one config object containing only ignores, no files. That removes those paths from the population entirely rather than from each object in turn. Since the resolved config for those paths already has 0 rules enabled, this changes no accept/reject semantics and cuts no coverage; it is measurable as a pure enumeration/parse saving.
Not verified here
- Whether a real
pnpm build tree reproduces the 83 MB figure — the number above is from synthetic bundle-shaped files, sized to a plausible built monorepo. No built tree was available in this container. - Whether any consumer deliberately relies on build output being in the population.
Source
Found while measuring #12261 (domain:devx seat, session af22b339-91b5-5814-8b9b-2453fc5b3f68).
Observation
eslint.config.mjslists**/dist/**,**/build/**,**/.next/**,**/.turbo/**in theignoresof every config object — but each of those objects also carries afileskey. In ESLint flat config anignoresalongsidefilesis object-scoped, not a global ignore: it stops that object from applying, it does not remove the file from the linted population. So build output stays in the sweep.Measured on
8649b398b8, Linux, Node 22.22.2 (the pinned toolchain:.nvmrc=22,engines.node=>=22.0.0,lint.ymlusessetup-nodewithnode-version: '22'):1. Build output is in the population. Dropping 20,000 synthetic
.jsfiles underpackages/<pkg>/dist/took the linted population from 5,142 → 25,142 files. Every one of them was linted.2. Those files can never produce a finding.
eslint --print-configon adist/*.js:Zero rules, and still the custom (
@typescript-eslint) parser — so each file is parsed in full to produce a guaranteed-empty result.3. The cost is not small. 83 MB of emitted-bundle-shaped
.js(25 files) placed under adist/directory, against the same source tree:dist/bundles+4.46 GB of peak RSS and +275 s of wall time, for zero reachable lint findings.
Why it matters
ignoresomits a build dir would start reporting on emitted bundles.lint.ymlrunspnpm install --frozen-lockfilethenpnpm lint, with no build step — the step comment says "Syntactic only, so no build step needed"), so CI lints ~5.1k source files. A developer who has runpnpm buildlints those plus every emitted bundle. Local and CI are measurably not running the same sweep, which is the thing the rootlintscript otherwise exists to guarantee.pnpm lintaborts with a V8 heap OOM (exit 134) at the default old-space limit — the script pins --stack-size but not --max-old-space-size #12261 — that card is about the memory symptom and its declared surface is thelintscript, so the ignore shape was out of scope there and is filed separately here. [finding]pnpm lintaborts with a V8 heap OOM (exit 134) at the default old-space limit — the script pins --stack-size but not --max-old-space-size #12261 is not addressed by this issue and remains open.Suggested direction (not verified)
Promote the build-output patterns to a global ignore — one config object containing only
ignores, nofiles. That removes those paths from the population entirely rather than from each object in turn. Since the resolved config for those paths already has 0 rules enabled, this changes no accept/reject semantics and cuts no coverage; it is measurable as a pure enumeration/parse saving.Not verified here
pnpm buildtree reproduces the 83 MB figure — the number above is from synthetic bundle-shaped files, sized to a plausible built monorepo. No built tree was available in this container.Source
Found while measuring #12261 (
domain:devxseat, sessionaf22b339-91b5-5814-8b9b-2453fc5b3f68).