fix(lint): run single-threaded by default and share one command with CI - #416
Merged
Conversation
`--concurrency=auto` opened `availableParallelism() >> 1` workers, and each one independently rebuilds the typescript-eslint program set rather than sharing it. Measured over this repo (1225 files, cold cache, 18-core host), peak RSS scales linearly at a constant ~4.7 GB per worker: off (4 GB heap) 40.6 s 4.74 GB OOM 2 (4 GB heap) 35.7 s 9.46 GB OOM 4 (4 GB heap) 48.4 s 18.62 GB ok auto (9 workers) 32.5 s 38.70 GB ok off (8 GB heap) 48.2 s 7.40 GB ok So the default asked for ~39 GB and, on a machine without that much free, fell into swap thrash; narrowing it to 2 did not help but OOM'd instead, because fewer workers means more files -- and more accumulated type information -- per heap. Concurrency only wins on a cold full run with genuinely free RAM. On a warm cache, which is the common case and what the pre-commit hook hits, single threaded is 1.9 s / 0.5 GB against 3.3 s / 3.8 GB at `auto`. Default to `--concurrency=off` with an inline 8 GB heap (pnpm 11 ignores `node-options` in `.npmrc`, so the script is the only place that reaches both local and CI), and drop `lint:ci` so both run the identical command. `LINT_CONCURRENCY` overrides it for anyone who wants to trade RAM for a faster cold run.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c2ee5531a2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
lucas77778
self-requested a review
August 6, 2026 04:17
lucas77778
approved these changes
Aug 6, 2026
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 free
to 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.
Problem
pnpm lintused--concurrency=auto, which opensavailableParallelism() >> 1workers (9 on an 18-core host). Each worker independently rebuilds the typescript-eslint program set instead of sharing it, so cost scales with worker count rather than being divided by it.Measured over this repo (1225 files, cold cache, 18-core host):
--concurrencyoff, default 4 GB heap2, default heap4, default heapauto(9 workers), default heapoff, 8 GB heapPeak RSS scales linearly at a constant ~4.7 GB per worker, and CPU time grows 4.8x from
offtoautowhile wall time barely moves.Two consequences:
2is not the safe direction. Fewer workers means more files, and more accumulated type information, per heap:2OOMs where4survives. This is the same failure as theERR_WORKER_OUT_OF_MEMORYthat broke every branch in 2026-07 (ci(lint): fix the intermittent ESLint worker OOM #312), and it exits non-zero fast enough to look like a fast successful run.Concurrency only pays on a cold full run with genuinely free RAM. On a warm cache — the common case, and what the pre-commit hook hits on every commit — single-threaded is 1.9 s / 0.5 GB against 3.3 s / 3.8 GB at
auto, because nine workers each pay startup and each read the whole cache file.Change
lint/lint:fixuse a cross-platform Node launcher that defaults to--concurrency=off, starts ESLint with--max-old-space-size=8192, and readsLINT_CONCURRENCYitself. The npm scripts contain no POSIX-only environment assignment or parameter expansion.lint:ciis removed; CI runspnpm lint, the identical command developers run.LINT_CONCURRENCYoverrides the default for anyone wanting to trade RAM for a faster cold run; set it to4in the invoking shell. ~4.7 GB per worker.docs/DEVELOPMENT.mdsaid concurrency "splits the type-aware program across workers";ci.ymlsaid it "duplicates ... instead of splitting". The measurements confirmci.yml— the doc is corrected and now carries the table.NODE_OPTIONS: --max-old-space-size=6144stays for the non-lint Node steps; lint no longer depends on it.Verification
pnpm check:ci(format + lint + typecheck) green in a full checkout.pnpm lintcold: exit 0, 56.5 s, peak RSS 7.30 GB — no OOM, which is itself the proof the launcher heap limit reaches ESLint.pnpm lintwarm: 1.9 s, peak RSS 0.50 GB.LINT_CONCURRENCY=4withpnpm lintwarm: 4.9 s — the override reaches ESLint (an invalid value fails ESLint's option validation loudly).