Rewritten. This issue originally recommended against wiring lint.yml, arguing it would be a no-op gate. That was wrong — the reasoning missed three error-level ratchets that were silently inert. The original analysis and the correction are preserved in the comments below; this body is the accurate statement of the defect. Fixed by #2928.
Two layers, the same shape as #2911
Layer 1 — the workflow never ran. lint.yml's only trigger was workflow_dispatch, so ESLint had never gated a PR.
Layer 2 — turbo run lint silently skips packages with no lint script. 7 of 45 had none, including apps/console, the largest surface in the repo. So even a wired workflow would have been blind to it. Identical to the type-check hole in #2911.
Why layer 1 was not harmless
eslint.config.js sets three object-ui/* rules to error specifically so a new violation fails CI:
| rule |
origin |
comment in config |
object-ui/no-synthetic-event-trigger |
ADR-0054 Phase 5 |
"Error so a new violation fails CI; the existing surfaces were converted … first, so this lints clean today." |
object-ui/no-try-catch-around-hook |
#2879 |
"Error so a tenth copy fails CI; all known sites were converted first, so this lints clean today." |
object-ui/no-inline-spec-config |
packages/types/src/objectql.ts |
"Error so a new inline mirror fails CI; the covered fields were converted first, so this lints clean today." |
Three separate deliberate efforts, each written on the assumption that CI enforces it, each having pre-cleaned its violations so the rule would lint clean. All three were worthless. The rule was authored; nothing ran it.
What it hid
apps/console (14 errors) and packages/runner (3) carried 17 ESLint errors — error severity under the repo's own config, not warnings — that nobody had ever seen. 12 are React-correctness bugs:
react-hooks/purity x8 — Date.now() called during render in ApprovalsInboxPage.tsx
react-hooks/static-components x4 in console, x3 in runner — components created during render, which React remounts every render: state loss, focus loss. Three of console's four are Settings pages.
Both rules are at error because the config downgrades five other React Compiler rules but deliberately not these two. Split out to #2927; they are pre-existing, not a regression.
Also: apps/site was not broken
Of the 25 errors first measured across the unlinted packages, 8 were artifacts:
- 6 in
apps/site/.source/ — fumadocs-mdx generated output, already gitignored
- 1 stale
eslint-disable-next-line @next/next/no-img-element — the Next plugin isn't in the flat config, so the directive itself errored as an unknown rule
- 1
apps/console/tailwind.config.js — TypeScript syntax in a .js file, so the base JS parser choked
Second time apps/site looked like debt purely because of generated artifacts (cf. #2924, where its 7 phantom type errors were a missing .next/types).
Verified by planting a hook inside try/catch in a linted package: pnpm lint exit 1, Failed: @object-ui/i18n#lint — impossible before.
Deliberately not done: --max-warnings
Measured composition of the 7,724 warnings:
| rule |
count |
share |
@typescript-eslint/no-explicit-any |
6,262 |
81% |
react-refresh/only-export-components |
486 |
6% |
@typescript-eslint/no-unused-vars |
322 |
4% |
react-hooks/set-state-in-effect |
252 |
3% |
react-hooks/exhaustive-deps |
177 |
2% |
react-hooks/refs |
111 |
1% |
four more react-hooks/* |
79 |
|
One stylistic rule is 81%, and five react-hooks/* rules are downgraded on purpose ("codebase predates these rules"). A blanket cap of 7,724 reads as a health signal while really tracking no-explicit-any. Left open rather than answered badly.
Promoting no-unused-vars to error (322 sites) was also dropped: #2911's actual failure mode was TS1192, now covered by the type-check gate from #2915. Unusedness itself has no demonstrated harm here, so the cleanup would be cosmetic.
Two layers, the same shape as #2911
Layer 1 — the workflow never ran.
lint.yml's only trigger wasworkflow_dispatch, so ESLint had never gated a PR.Layer 2 —
turbo run lintsilently skips packages with nolintscript. 7 of 45 had none, includingapps/console, the largest surface in the repo. So even a wired workflow would have been blind to it. Identical to the type-check hole in #2911.Why layer 1 was not harmless
eslint.config.jssets threeobject-ui/*rules toerrorspecifically so a new violation fails CI:object-ui/no-synthetic-event-triggerobject-ui/no-try-catch-around-hookobject-ui/no-inline-spec-configpackages/types/src/objectql.tsThree separate deliberate efforts, each written on the assumption that CI enforces it, each having pre-cleaned its violations so the rule would lint clean. All three were worthless. The rule was authored; nothing ran it.
What it hid
apps/console(14 errors) andpackages/runner(3) carried 17 ESLint errors — error severity under the repo's own config, not warnings — that nobody had ever seen. 12 are React-correctness bugs:react-hooks/purityx8 —Date.now()called during render inApprovalsInboxPage.tsxreact-hooks/static-componentsx4 in console, x3 in runner — components created during render, which React remounts every render: state loss, focus loss. Three of console's four are Settings pages.Both rules are at
errorbecause the config downgrades five other React Compiler rules but deliberately not these two. Split out to #2927; they are pre-existing, not a regression.Also:
apps/sitewas not brokenOf the 25 errors first measured across the unlinted packages, 8 were artifacts:
apps/site/.source/— fumadocs-mdx generated output, already gitignoredeslint-disable-next-line @next/next/no-img-element— the Next plugin isn't in the flat config, so the directive itself errored as an unknown ruleapps/console/tailwind.config.js— TypeScript syntax in a.jsfile, so the base JS parser chokedSecond time
apps/sitelooked like debt purely because of generated artifacts (cf. #2924, where its 7 phantom type errors were a missing.next/types).Fixed in #2928
lint.ymlruns onpull_request+pushscripts/check-lint-coverage.mjs— every package lints or is a declared gap; the list only shrinksDEBT, tracked in console + runner: 17 ESLint errors that were never seen, incl. 12 React-correctness bugs #2927Verified by planting a hook inside try/catch in a linted package:
pnpm lintexit 1,Failed: @object-ui/i18n#lint— impossible before.Deliberately not done:
--max-warningsMeasured composition of the 7,724 warnings:
@typescript-eslint/no-explicit-anyreact-refresh/only-export-components@typescript-eslint/no-unused-varsreact-hooks/set-state-in-effectreact-hooks/exhaustive-depsreact-hooks/refsreact-hooks/*One stylistic rule is 81%, and five
react-hooks/*rules are downgraded on purpose ("codebase predates these rules"). A blanket cap of 7,724 reads as a health signal while really trackingno-explicit-any. Left open rather than answered badly.Promoting
no-unused-varstoerror(322 sites) was also dropped: #2911's actual failure mode was TS1192, now covered by the type-check gate from #2915. Unusedness itself has no demonstrated harm here, so the cleanup would be cosmetic.