Skip to content

Cut local lint peak memory from 15.4 GB to 4.3 GB (Fixes #3387) - #3395

Merged
acoliver merged 4 commits into
dev/0.12.0from
issue3387
Aug 30, 2026
Merged

Cut local lint peak memory from 15.4 GB to 4.3 GB (Fixes #3387)#3395
acoliver merged 4 commits into
dev/0.12.0from
issue3387

Conversation

@acoliver

@acoliver acoliver commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

TLDR

npm run lint asked Node for a 12 GB heap and exhausted it, so contributors on 16 GB machines could not run the repo's own lint gate. It now peaks at 4,565,237,760 B (4.25 GiB) and finishes in 149.25 s, against 16,586,620,928 B (15.45 GiB) / 242.74 s before. Cold, no cache, same machine. The default heap in package.json and scripts/run-lint.ts drops from 12288 to 6144, which is what the largest group actually needs.

The cause was not what the issue assumed, so the two things reviewers should look hardest at are the diagnosis in project-plans/issue3387/README.md and the coverage proof below.

Dive Deeper

The issue's hypothesis did not survive measurement

The issue attributes the cost to "the sum of every package's type program" and proposes per-package invocations. That does not hold. Capped at a 6 GB heap, per-package runs of cli, agents, providers, core, mcp and telemetry all died with Reached heap limit. Partitioning alone cannot reach the target.

What was actually happening

The root tsconfig.json declares no include, so TypeScript's default **/* applies and the project spans the entire repository.

Six package tsconfigs excluded individual test files that fail tsc (cli 303 entries, providers 247, agents 206, core 178, mcp 59, telemetry 6). ESLint still linted those files. typescript-eslint walked up from each one, found its package project did not contain it, and landed on the root config, building a whole-monorepo program to serve it.

On packages/cli/src/config/config.test.ts, one ESLint invocation, nothing else changed:

tsconfig state peak RSS wall
listed in packages/cli/tsconfig.json exclude 8,769,847,296 B 46.96 s
removed from that exclude 1,640,644,608 B 7.06 s

Confirmation that the root config was the provider: pointing the root include at a nonexistent path makes ESLint report config.test.ts was not found by the project service. The cost is flat per process, not per file. One orphan file cost 8,769,847,296 B; six cost 8,692,482,048 B. Same program either way.

Change 1: give every source file its own package project

Each affected package moves its exclusion list into a sibling tsconfig.noemit.json that extends tsconfig.json, and points its typecheck script there. tsc verifies the same files as before.

Verified rather than asserted: the resolved files arrays from tsc --showConfig are byte-identical before and after in all six packages (426, 2364, 683, 374, 839, 90 files respectively; zero added, zero removed).

references is repeated in those configs on purpose. TypeScript does not inherit it through extends, and dropping it turns referenced project sources into ordinary inputs of a composite project, which fails typecheck with TS6059. That was caught during development, not shipped.

Per-package effect, all still linting clean:

package before after
telemetry 7,566,065,664 B / 36.70 s 809,730,048 B / 3.50 s
mcp 6,564,397,056 B / 46.32 s 1,236,713,472 B / 4.46 s
agents 9,284,255,744 B / 45.05 s 2,889,580,544 B / 15.08 s
providers 8,070,938,624 B / 57.82 s 3,798,482,944 B / 18.75 s
core 7,914,766,336 B / 60.90 s 3,687,792,640 B / 20.42 s
cli 12,191,252,480 B / 86.67 s 6,060,621,824 B / 37.94 s

Change 2: partition the full run

scripts/run-lint.ts emits one ESLint process per packages/<pkg> directory plus one for . with --ignore-pattern 'packages/*/**'. The partition is read from the filesystem, so a new package is linted the day it appears without editing the runner.

The pattern is packages/*/**, not packages/**. Review caught that the latter also drops a file sitting directly in packages/, which no package group covers because discovery yields directories only. Reproduced against ESLint 9 and fixed.

Also in the runner:

  • Lint failures accumulate across groups and the first failing exit code is returned at the end, so one broken package cannot hide findings in the other sixteen. A signal termination still aborts immediately, because an interruption is not a lint result.
  • --no-error-on-unmatched-pattern applies only to the per-package groups, where packages/lsp is legitimately all-ignored. Scoped targets and the rest-of-tree group keep the error, so a stale CI target still fails loudly.
  • All groups share node_modules/.cache/eslint, the exact path CI caches. Per-group cache files would have silently disabled CI lint caching. Splitting was unnecessary anyway: ESLint merges into an existing cache rather than pruning entries for files the current run did not visit, verified directly.

Coverage is proved, not argued

The acceptance criterion is that a bare npm run lint does not lint fewer files. Checked by collecting --format json from eslint . and from every partitioned command and comparing filePath sets across the real repository:

eslint .          : 5386 files
partitioned union : 5386 files
linted before but not after : 0
linted after but not before : 0
linted by more than one group: 0

What is not in scope

lint:ci remains a single-process eslint . and still asks for 12 GB. Change 1 brings it from 15.45 GiB to 13.41 GiB, but routing it through the runner requires changing scripts/eslint-guard/config-scanner.ts, which demands a literal eslint token carrying --max-warnings 0 in that script. scripts/pre-push-check.sh and npm run preflight both use it. Recorded as a follow-up in the plan.

The issue's stretch goal of 4 GB is not reached. packages/cli/tsconfig.json pulls providers, auth, mcp, settings and ide-integration sources into one program; splitting that is a separate change.

Review also proposed pinning the moved files to the root lib/types so type-aware rules see ES2023. Rejected, with reasoning recorded in the plan: a test file in packages/agents runs under agents' settings, so linting it as though ES2023 existed reports on a program that does not. Files that were never excluded already linted under their package's settings, so this makes the excluded ones match their siblings rather than giving any file weaker treatment. Lint is clean before and after over the identical 5386-file set, so no diagnostic was suppressed.

Reviewer Test Plan

  1. npm run lint from a clean checkout. It should print 17 labelled groups and finish well under the old 12 GB. To see the ceiling: /usr/bin/time -l npm run lint on macOS, /usr/bin/time -v on Linux.
  2. Confirm coverage did not shrink:
    ./node_modules/.bin/eslint . --format json -o /tmp/full.json
    
    then run each group the runner prints and compare the union of filePath values against /tmp/full.json.
  3. npm run typecheck should behave exactly as on main. To check parity directly, compare npx tsc -p tsconfig.noemit.json --showConfig in a changed package against npx tsc -p tsconfig.json --showConfig on main; the files arrays match.
  4. Break the invariant on purpose and watch the guards fire:
    • add any own-package file or glob to a package tsconfig.json exclude, then bun test scripts/tests/tsconfig-project-coverage.test.ts;
    • change PACKAGES_IGNORE_PATTERN back to packages/**, then bun test scripts/tests/run-lint-partition.test.ts.
      Both were verified to fail on those mutations and pass when restored.
  5. bun test scripts/tests/run-lint.test.ts scripts/tests/run-lint-partition.test.ts scripts/tests/tsconfig-project-coverage.test.ts (36 + 8 + 7 tests). The partition tests spawn the real ESLint binary against a fixture containing a file directly in packages/, a dot-named package and ordinary packages. The execution tests spawn real child processes to observe failure accumulation, exit-code fidelity and signal abort.

Testing Matrix

🍏 🪟 🐧
npm run
npx
Docker
Podman - -
Seatbelt - -

Verified on macOS: npm run test, npm run lint, npm run typecheck, npm run format, npm run build, and the stepfun-37 CLI smoke run all pass. The runner uses no platform-specific paths, but the ESLint ignore-pattern behaviour has only been exercised on macOS.

Linked issues / bugs

Fixes #3387

Summary by CodeRabbit

  • Bug Fixes

    • Improved lint reliability by checking packages independently, continuing after failures, and preserving accurate error reporting.
    • Reduced lint memory usage to help prevent process termination.
    • Improved handling of targeted lint runs and termination signals.
  • Type Checking

    • Added dedicated no-emit configurations across packages.
    • Expanded validation coverage while preventing build artifacts during type checks.
  • Tests

    • Added coverage for lint partitioning, command execution, signal handling, and TypeScript project coverage.

`npm run lint` asked for a 12 GB heap and exhausted it, putting the repo's
own lint gate out of reach for contributors on 16 GB machines.

The issue attributed the cost to the sum of every package's type program and
proposed per-package invocations. Measurement did not support that: capped at
a 6 GB heap, per-package runs of cli, agents, providers, core, mcp and
telemetry all died.

The driver is the root tsconfig.json. It declares no `include`, so
TypeScript's default `**/*` applies and the project spans the whole
repository. Six package tsconfigs excluded individual test files that fail
`tsc`; ESLint still linted them, typescript-eslint found no package project
containing them, and fell back to that whole-repo program. On
packages/cli/src/config/config.test.ts, one invocation, nothing else changed:
8,769,847,296 B / 46.96 s while excluded, versus 1,640,644,608 B / 7.06 s
once included. The cost is flat per process, so one such file paid it in
full.

Two changes:

- Each affected package moves its exclusion list into a sibling
  tsconfig.noemit.json that extends tsconfig.json, and points its typecheck
  script there. `tsc` reads the same file set as before, verified by
  comparing resolved `files` from --showConfig: identical in all six
  packages. `references` is repeated in those configs because TypeScript
  does not inherit it through `extends`, and losing it fails typecheck with
  TS6059.

- run-lint.ts partitions a full run into one ESLint process per
  packages/<pkg> plus one for `.` with packages ignored. The halves are exact
  complements, so the union is the file set of `eslint .`, and the partition
  is read from the filesystem so a new package needs no runner edit. Scoped
  runs are partitioned per target for the same reason. Each group gets its
  own cache file, since ESLint rewrites the whole cache with just the files
  it linted. Lint failures now accumulate rather than stopping at the first
  group, so one broken package cannot hide findings in the other sixteen; a
  signal termination still aborts immediately.

Measured end to end, cold, no cache: 16,586,620,928 B / 242.74 s before,
4,610,539,520 B / 161.62 s after. Peak drops 3.6x, wall clock drops 33%.
DEFAULT_HEAP_MB and the package.json numbers drop to 6144, which is what the
largest group actually needs.

lint:ci is unchanged and remains a single-process `eslint .`; routing it
through the runner requires changing scripts/eslint-guard, which is tracked
as follow-up in the plan.
Coverage hole, found by review and reproduced against ESLint 9: the
rest-of-tree group ignored `packages/**`, which drops a file sitting directly
in `packages/` as well as the package subtrees. `readPackageDirs` yields
directories only, so no group covered such a file. The complement is now
`packages/*/**`, which excludes the contents of each immediate package
directory and leaves direct entries to the rest group. The repository has no
such file today, which is why the whole-tree file-set comparison did not
catch it.

The unit test for coverage was circular: it fed package discovery back into
the builder and asserted the same names came out, so it passed with the hole
present. scripts/tests/run-lint-partition.test.ts now runs the real ESLint
binary over a real fixture containing a direct file in `packages/`, a
dot-named package and ordinary packages, and compares the file set of
`eslint .` against the union of the partitioned commands. Reverting the
pattern to `packages/**` makes it fail.

Per-group ESLint cache files are reverted to the single shared location.
.github/workflows/ci.yml caches that exact path, so suffixed files would have
silently disabled CI lint caching. The split was unnecessary anyway: ESLint
merges into an existing cache rather than pruning entries for files the
current run did not visit, verified by running two package groups against one
cache file and confirming both packages' entries survived.

`--no-error-on-unmatched-pattern` is now applied only to the per-package
groups of a full run, whose targets are read from the filesystem and where
`packages/lsp` is legitimately all-ignored. Scoped targets come from CI's
affected-target selector, so a stale or mistyped one fails loudly again
instead of passing with zero files linted.

Execution semantics were claimed but untested. `executeLintCommands` is
extracted and exercised with real child processes: every group runs when an
earlier one fails, the first failing exit code propagates rather than the
last, and a signal termination aborts the remaining groups.

The tsconfig guard checked only literal existing file entries in the raw
`exclude` array, so a glob or a narrowed `include` could orphan files while it
stayed green. It now resolves each package project with TypeScript's config
parser and asserts every type-aware-linted source file under
`packages/<pkg>/src` is in its own package's resolved file set, allowing only
the `allowDefaultProject` entry eslint.config.js declares. Adding
`src/debug/**` to a package exclude makes it fail.

The noemit configs now set `noEmit` themselves, so a direct
`tsc -p tsconfig.noemit.json` cannot write build output into the sources, and
their header comments describe the actual split rather than implying the
whole list moved.

Reviewer's proposal to pin the moved files to the root `lib`/`types` is
rejected and the reasoning recorded in the plan: a test file in
packages/agents runs under agents' settings, so linting it as though ES2023
were available reports on a program that does not exist. Files that were not
excluded already linted under their package's settings; this makes the
excluded ones match their siblings rather than giving any file weaker
treatment.

Final measurement on this tree: 4,565,237,760 B peak RSS in 149.25 s, against
16,586,620,928 B in 242.74 s before.
@github-actions github-actions Bot added the maintainer:e2e:ok Trusted contributor; maintainer-approved E2E run label Aug 28, 2026
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 26500cda-81d8-4d3c-b0e2-9b994fcbea93

📥 Commits

Reviewing files that changed from the base of the PR and between 6240249 and 39a7f9f.

📒 Files selected for processing (4)
  • scripts/run-lint.ts
  • scripts/tests/run-lint-partition.test.ts
  • scripts/tests/run-lint.test.ts
  • scripts/tests/tsconfig-project-coverage.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • scripts/tests/run-lint-partition.test.ts
  • scripts/tests/tsconfig-project-coverage.test.ts
  • scripts/tests/run-lint.test.ts
  • scripts/run-lint.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

The lint runner now partitions full runs by package and executes groups sequentially. Package typecheck scripts use dedicated tsconfig.noemit.json files. Base configs retain minimal exclusions for ESLint project resolution. Tests verify lint coverage and TypeScript project ownership.

Changes

Lint execution and validation

Layer / File(s) Summary
Partitioned lint execution
package.json, scripts/run-lint.ts
Full runs use per-package commands and a complementary root-tree command. Scoped runs use separate deduplicated targets. Commands share one cache and use a 6144 MB heap.
Lint partition and execution validation
scripts/tests/run-lint-partition.test.ts, scripts/tests/run-lint.test.ts, scripts/tests/issue-2994-lint-scoped.bun.test.ts
Tests verify complete coverage, command construction, failure handling, signal handling, cache reuse, scoped targets, and heap normalization.

Package TypeScript projects

Layer / File(s) Summary
Dedicated package typecheck projects
packages/*/package.json, packages/*/tsconfig.json, packages/*/tsconfig.noemit.json
Package typecheck scripts target dedicated no-emit configs. Base configs keep minimal exclusions, while no-emit configs contain test exclusions and required project references.
TypeScript project coverage checks
scripts/tests/tsconfig-project-coverage.test.ts
Tests verify that type-aware source files belong to package projects and that no-emit configurations, exclusions, references, and scripts remain aligned.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to 39a7f

This change reduces lint memory usage and partitions linting while preserving the documented file and typecheck scope; no actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #3387: they reduce cold lint peak RSS to 4.25 GiB, lower the documented heap setting, preserve all 5,386 linted files without overlap, record before-and-after measurements, a…
Out of Scope Changes check ✅ Passed The configuration changes, lint-runner partitioning, memory reduction, and supporting tests directly support issue #3387. The explicitly retained single-process lint:ci path is documented as a separat…
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 22 functions across 5 files.
Description check ✅ Passed The description is complete and follows the repository template. It explains the motivation, implementation, measured results, scope boundaries, reviewer test plan, testing matrix, and linked issue.
Title check ✅ Passed The title clearly summarizes the primary change: reducing local lint peak memory. It also identifies the related issue and remains concise.
Full details: Linked Issues check

Explanation

The changes satisfy issue #3387: they reduce cold lint peak RSS to 4.25 GiB, lower the documented heap setting, preserve all 5,386 linted files without overlap, record before-and-after measurements, and state the wall-clock result.

Full details: Out of Scope Changes check

Explanation

The configuration changes, lint-runner partitioning, memory reduction, and supporting tests directly support issue #3387. The explicitly retained single-process lint:ci path is documented as a separate follow-up, not an unrelated change.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue3387

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR changes 25 file(s).

  • packages/mcp/tsconfig.json: Removed most test files from the TypeScript project include list in packages/mcp/tsconfig.json, retaining only fileUtils.test.ts. This reduces TypeScript's processing scope to lower lint memory consumption from 15.4 GB to 4.3 GB as part of fix npm run lint needs a 12 GB heap, which puts local linting out of reach for many contributors #3387.
  • packages/agents/package.json: Updated the typecheck script in package.json to explicitly pass -p tsconfig.noemit.json to tsc --noEmit, ensuring TypeScript uses a dedicated no-emit configuration file.
  • packages/cli/package.json: In packages/cli/package.json, the typecheck script was changed from tsc --noEmit to tsc --noEmit -p tsconfig.noemit.json, explicitly pointing TypeScript at a dedicated no-emit config instead of relying on default project resolution. This reduces lint memory usage by avoiding broader default compilation context.
  • scripts/tests/tsconfig-project-coverage.test.ts: Adds a new test suite that enforces every ESLint-linted source file belongs to its own package's TypeScript project, preventing root tsconfig sprawl that inflated local lint peak memory from ~15 GB to ~4.3 GB. It validates project file membership, noemit config correctness, and typecheck script wiring across all packages using real tsconfigs and ESLint config.
  • packages/mcp/package.json: Updated the mcp package test script to use an explicit no-emit TypeScript config file instead of the default tsc --noEmit, likely to reduce memory usage during type checking.
  • packages/cli/tsconfig.noemit.json: New dedicated tsconfig.noemit.json for the CLI package that narrows the tsc --noEmit program scope to avoid repo-wide type-checking, explicitly excludes cross-package test files, and retains project references to prevent TS6059 errors.
  • packages/agents/tsconfig.json: The TypeScript configuration exclude list was drastically simplified from dozens of specific test file paths to only "node_modules" and "dist", removing explicit exclusions for agent, api, compression, core, scheduler, and tool test files.
  • packages/telemetry/package.json: Updated the telemetry package's typecheck script to explicitly pass -p tsconfig.noemit.json to tsc --noEmit, ensuring the dedicated no-emit TypeScript config is used instead of implicit defaults.
  • packages/core/tsconfig.noemit.json: Adds a new tsconfig.noemit.json dedicated to tsc --noEmit verification. It extends the package tsconfig with noEmit: true and a comprehensive exclude list covering cross-package tests and local test files. This prevents TypeScript from falling back to the root config and compiling the entire repository, which previously consumed ~7 GB and ~40 seconds per ESLint process. The references array is duplicated because TypeScript does not inherit it through extends.
  • packages/providers/tsconfig.json: Removes the bulk of test file paths from the TypeScript project include list, leaving only one MCP client test entry. This reduces the number of files TypeScript processes during lint/type-check, directly lowering local lint peak memory from 15.4 GB to 4.3 GB as described in the PR title.
  • scripts/tests/issue-2994-lint-scoped.bun.test.ts: Test helper refactored to support scoped lint partitioning (issue npm run lint needs a 12 GB heap, which puts local linting out of reach for many contributors #3387). compose renamed to composeAll and now returns all target command argv arrays (ReadonlyArray<readonly string[]>) instead of only the first. A backward-compatible compose wrapper delegates to composeAll()[0]. Test assertions updated to expect one ESLint command per target, with shared flags forwarded to every command.
  • packages/agents/tsconfig.noemit.json: A new tsconfig.noemit.json is added to the agents package to scope tsc --noEmit verification. It extends the package tsconfig and excludes test files and build outputs, preventing TypeScript from scanning the entire repository during linting. This addresses issue npm run lint needs a 12 GB heap, which puts local linting out of reach for many contributors #3387 by reducing local lint peak memory from 15.4 GB to 4.3 GB. The root tsconfig.json keeps only minimal exclusions, while this file carries the complete file list. The overlap with the base tsconfig is deliberate because extends replaces exclude wholesale.
  • scripts/tests/run-lint-partition.test.ts: New end-to-end test file for the lint partition (npm run lint needs a 12 GB heap, which puts local linting out of reach for many contributors #3387). Validates that partitioned ESLint commands cover exactly the same files as a root eslint . invocation, including edge cases like ungrouped files directly under packages/ and dot-named package directories. Also verifies execution semantics: all groups run despite earlier failures, the first failing exit code is returned, successful runs resolve, and signal aborts stop subsequent groups. Uses real ESLint binary and child processes rather than mocks.
  • packages/telemetry/tsconfig.json: Simplified the telemetry package TypeScript configuration by removing four specific test file exclusions from the exclude list, retaining only node_modules and dist.
  • packages/mcp/tsconfig.noemit.json: Adds a new tsconfig.noemit.json for the MCP package to restrict TypeScript's type-check program scope. By explicitly excluding test files and external tooling tests, it prevents TypeScript from loading the entire repository (which cost ~7 GB and ~40s per ESLint process), reducing local lint peak memory from 15.4 GB to 4.3 GB.
  • package.json: Reduced the Node.js heap limit for local lint commands from 12,288 MB to 6,144 MB in the lint and lint:fix scripts, cutting peak memory usage during local lint runs.
  • packages/providers/package.json: Updated the typecheck npm script to use a dedicated TypeScript configuration file (tsconfig.noemit.json) instead of the default project config. This narrows the type-checking scope, reducing peak memory usage during linting as part of the PR's broader memory optimization effort.
  • packages/cli/tsconfig.json: Removed a large block of test file paths from the TypeScript configuration, retaining only one provider test file. This narrows the compiler/lint scope to cut local lint peak memory from 15.4 GB to 4.3 GB.
  • scripts/run-lint.ts: Replaces the single full-tree ESLint invocation with a partitioned run: one process per packages/<pkg> directory plus one for the rest of the tree. Peak heap drops from 12 GB to 6 GB per process, cutting local lint peak memory from 15.4 GB to 4.3 GB. The partition is derived from the filesystem via readdirSync so new packages are picked up automatically. Scoped CI runs are also split per target. Adds readPackageDirs and executeLintCommands exports, and changes failure behavior so all groups run and the first failure is re-thrown at the end.
  • packages/core/package.json: The typecheck script in packages/core/package.json was modified to pass -p tsconfig.noemit.json to the initial tsc --noEmit command, narrowing the typecheck scope. This reduces local lint peak memory usage from ~15.4 GB to ~4.3 GB.
  • packages/telemetry/tsconfig.noemit.json: Added packages/telemetry/tsconfig.noemit.json to separate the tsc --noEmit verification file set from the main tsconfig.json. Previously, excluded test files in this package fell back to the root tsconfig, causing TypeScript to build the entire repository program (~7 GB, ~40s per ESLint process). The new config explicitly excludes node_modules, dist, and specific test files while enforcing noEmit, preventing the full-repo type-check overhead and cutting local lint peak memory from 15.4 GB to 4.3 GB.
  • packages/providers/tsconfig.noemit.json: Adds packages/providers/tsconfig.noemit.json to separate type-check verification from ESLint source resolution. The new config extends the package tsconfig, enforces noEmit: true, and explicitly excludes tests and cross-package files so tsc --noEmit does not fall back to the root config and rebuild the entire repo, cutting local lint peak memory from ~15.4 GB to ~4.3 GB.
  • packages/core/tsconfig.json: Removes the majority of test files from the TypeScript include array, retaining only one settings test file. This reduces the file scope processed during linting/type checking, directly addressing the memory issue described in PR Cut local lint peak memory from 15.4 GB to 4.3 GB (Fixes #3387) #3395.
  • scripts/tests/run-lint.test.ts: Updated behavioral tests for the lint runner's new partitioned execution model (issue npm run lint needs a 12 GB heap, which puts local linting out of reach for many contributors #3387). Full runs now produce one ESLint invocation per package directory plus a rest-of-tree group, instead of a single root command. Added tests for package sorting, real package discovery, ignore-pattern handling, and shared cache locations. Scoped runs now emit one command per explicit target. Heap normalization expectations updated from 12GB to 6GB. Interfaces extended with label, packageDirs, and readPackageDirs to support partition verification.
  • project-plans/issue3387/README.md: New project plan document for issue npm run lint needs a 12 GB heap, which puts local linting out of reach for many contributors #3387 diagnosing why npm run lint peaked at 15.4 GB RSS. Identifies root cause as the root tsconfig.json implicitly spanning the whole repo, causing TypeScript's project service to build a full-monorepo program for orphan test files. Phase 1 moves package-specific test exclusions into tsconfig.noemit.json; Phase 2 partitions the lint run into per-package ESLint invocations. Includes measured before/after results, accepted behavior criteria, rejected alternatives, and behavioral test descriptions.

Changes

Layer File(s) Summary
tsconfig packages/mcp/tsconfig.json, packages/agents/tsconfig.json, packages/providers/tsconfig.json, packages/cli/tsconfig.json, packages/core/tsconfig.json, packages/telemetry/tsconfig.json, packages/mcp/tsconfig.noemit.json, packages/agents/tsconfig.noemit.json, packages/core/tsconfig.noemit.json, packages/providers/tsconfig.noemit.json, packages/telemetry/tsconfig.noemit.json, packages/cli/tsconfig.noemit.json Narrows TypeScript project scope by removing broad test-file inclusion and adding dedicated no-emit configs, preventing repo-wide type-check programs during lint.
lint scripts/run-lint.ts, package.json, packages/cli/package.json, packages/agents/package.json, packages/mcp/package.json, packages/providers/package.json, packages/core/package.json, packages/telemetry/package.json Partitions ESLint runs per package, lowers per-process heap limits, and wires typecheck scripts to scoped tsconfig.noemit files to cut lint peak memory.
tests scripts/tests/tsconfig-project-coverage.test.ts, scripts/tests/issue-2994-lint-scoped.bun.test.ts, scripts/tests/run-lint.test.ts, scripts/tests/run-lint-partition.test.ts Adds regression tests enforcing package-local TypeScript project membership, lint partition coverage, and partitioned lint execution semantics.
docs project-plans/issue3387/README.md Records the issue analysis, root cause, phased solution, and measured before/after memory results for the lint memory optimization.

Magnitude

🎯 4 (XL)
2299 additions, 774 deletions, 25 changed files across 6 packages, 0 acceptance criteria

Related

No related items found.

Pre-merge Checks

Check Status Note
Title Clear and specific: states the measurable outcome (15.4 GB → 4.3 GB), the affected command, and the linked issue.
Description Includes TLDR, Dive Deeper, Reviewer Test Plan, Testing Matrix, and Linked issues / bugs, with detailed evidence, reproduction steps, and guard tests.
Linked Issues Actual changes meet the core acceptance criteria: local npm run lint target heap is reduced, full-file coverage is preserved (5386 files before/after), before/after measurements are recorded, and wall-clock impact is explicitly stated. New tests and a project plan document also support verification.
Out of Scope lint:ci is intentionally left on the old 12 GB single-process path and is recorded as a follow-up. The 4 GB stretch goal is not yet reached; further splitting of packages/cli/tsconfig.json is deferred.

Walkthrough generated by LLxprt PR Review. Planner issue: #2256

scripts/tests/issue-2994-lint-scoped.bun.test.ts composed the runner argv and
asserted the arguments of `commands[0]`, which encoded the old behavior of one
ESLint process carrying every scoped target. A scoped run is now one process
per target, so those three assertions described a shape the runner no longer
produces.

They now assert the argv of every command: one per target, `--fix` forwarded
to each, and the shared cache flags on each. This is the same coverage
expressed against the current contract, not a relaxation. Caught by CI's
scripts shard; missed locally because the file was not in the set I ran.
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

OpenCodeReview — automatic reviews suspended

Automatic OCR reviews are suspended for this PR after 2 of 2 automatic reviews.

To get more reviews you can:

  • Check the box below to re-enable automatic reviews (resets the counter), or

  • Comment /review, /ocr, or /open-code-review to request a single review on demand.

  • Re-enable automatic reviews


OpenCodeReview — PR #3395

  • Reviewed head SHA: 624024986db370054c0ac5329cd77eba7d0239c3
  • Merge base: 2fadb59ac222308eee31e367a1c5b736f9ee7871
  • Range: full from 2fadb59ac222308eee31e367a1c5b736f9ee7871
  • Range fallback: checkpoint-missing
  • Scope: selected 25 file(s), +2292/-774; cumulative 25 file(s), +2292/-774
  • Tokens: 0 total (0 input, 0 output, 0 cache)
  • OCR version: open-code-review v1.8.4 (e78474478) linux/amd64 built at: 2026-08-01T03:27:37Z https://github.com/alibaba/open-code-review
  • Phase: review
  • Exit code: 1
  • Run: https://github.com/vybestack/llxprt-code/actions/runs/33138387795
  • OCR failed to run or parse output.
  • Artifacts: ocr-review-output contains raw JSON, stdout, stderr, preview, phase, and exit-code diagnostics.

OCR stderr excerpt

(empty)

OCR preflight excerpt

model=step-3.7-flash
provider-url=configured
Source: OCR environment
URL:    [REDACTED]
Model:  step-3.7-flash
I am open-code-review, a code review assistant developed by Alibaba that runs in the user's local command-line environment to assist with code review related tasks.
✓ Connection test successful

OCR preview stderr excerpt

(empty)
  • WARNING: Changed-file coverage 0/24 preview files covered is below the 90% threshold.

CodeRabbit's docstring-coverage check reported 70% against an 80% threshold
over the functions this PR touches. Adds short comments to the runner's
command factory and CLI entry point, and to the test helpers that load the
real runner module, read a tsconfig exclude list, enumerate package
directories and walk a package source tree.
@acoliver
acoliver changed the base branch from main to dev/0.12.0 August 28, 2026 03:47
@acoliver acoliver added this to the 0.12.0 milestone Aug 28, 2026
@acoliver
acoliver merged commit cbd9f83 into dev/0.12.0 Aug 30, 2026
44 checks passed
acoliver added a commit that referenced this pull request Aug 30, 2026
Cover packages/*/test-bun/ in npm run typecheck (Fixes #2995)

Conflict resolution: #3395 moved the agents/cli/providers typecheck scripts
onto a dedicated tsconfig.noemit.json project to cut lint/typecheck peak
memory, while this PR chained a second pass over tsconfig.test-bun.json.
Both intents are kept by chaining the two projects:
  tsc --noEmit -p tsconfig.noemit.json && tsc --noEmit -p tsconfig.test-bun.json
storage/tools auto-merged because they have no noemit project.
acoliver added a commit that referenced this pull request Aug 30, 2026
…reads

Integration fallout between #3303 and #3387/#3395. #3303 added
useBracketedPaste.test.tsx next to the useBracketedPaste.test.ts that was
already on dev. When two files in a directory differ only by extension,
TypeScript's include resolution keeps the higher-priority one (.ts) and
silently drops the other, so the new file was never typechecked: `tsc -p
packages/cli/tsconfig.json --showConfig` listed the .ts and not the .tsx.
#3395's tsconfig-project-coverage guard reported it as an orphan, and lint
failed with "was not found by the project service".

The new file contains no JSX, so it never needed .tsx. It also supersedes the
older test rather than complementing it: both cover the same hook, but the new
one diffs listener identity sets instead of counting, asserts that the
registered cleanup is one shared reference and that invoking it really emits
the disable sequence, and adds a repeated mount/unmount leak check. It is
therefore kept as useBracketedPaste.test.ts, replacing the weaker file.

The only thing the old file had that the new one lacks is a local
IS_REACT_ACT_ENVIRONMENT assignment, which packages/cli/bun-test-setup.ts
already sets globally for every CLI test.

Verified: 4 tests pass, tsconfig-project-coverage is green, no other
.ts/.tsx basename collisions exist in packages/.
acoliver added a commit that referenced this pull request Aug 30, 2026
…onfigs as JSONC

Two failures from the full Bun suite after the Wave A/B merges.

1. release-like pack smoke died with EEXIST creating the work-copy
   node_modules symlink. #3388 moved that link out of packReleaseLikeCli and
   into copyRepoExcludingGenerated, via a new linkRepoNodeModules helper that
   guards both the source and the destination. dev had meanwhile edited the
   inline call it replaced (adding a win32 'junction' : 'dir' ternary), so the
   textual merge kept dev's inline block *and* took #3388's helper, and the
   link was attempted twice. Removing the inline block restores exactly the
   shape #3388 intended; the surviving helper is strictly better, since it is
   idempotent and returns instead of throwing when node_modules is absent.
   Note this deliberately follows #3388 in dropping the old hard precondition.

2. The #3360 coverage guard parsed tsconfig.test-bun.json with JSON.parse and
   threw on the comments this branch added to the agents config. tsconfig is
   JSONC and this repository comments its tsconfigs freely, so the guard was
   one comment away from breaking regardless. It now uses ts.readConfigFile,
   matching the parseTsconfig helper #3395 added for the same reason.
   package.json parsing is left on JSON.parse, which is correct: manifests are
   strict JSON.

Verified: both previously failing files pass (release smoke 5/5, coverage
guard 12/12).
acoliver added a commit that referenced this pull request Aug 30, 2026
Remove the vestigial gemini-cli Declarative Agent Framework (Fixes #3152)

Conflict: packages/agents/tsconfig.json "exclude". This PR still carried the
long per-file exclusion list that used to live there; #3395 has since moved
that list into tsconfig.noemit.json, deliberately leaving tsconfig.json
excluding only node_modules and dist so typescript-eslint resolves every
in-package file against its own project. Took the post-#3395 form.

Follow-on: this PR deletes packages/agents/src/agents/, and seven of those
files were named in the relocated exclusion list, which would have left
tsconfig.noemit.json pointing at paths that no longer exist. Removed those
seven entries:

  src/agents/__tests__/executorRun.characterization.test.ts
  src/agents/executor.execution.test.ts
  src/agents/executor.recovery.test.ts
  src/agents/executor.stream-idle-timeout.test.ts
  src/agents/executor.termination-conditions.test.ts
  src/agents/executor.test.ts
  src/agents/invocation.test.ts

Verified: tsconfig-project-coverage 7/7, agents noemit project compiles,
npm run typecheck clean.
acoliver added a commit that referenced this pull request Aug 30, 2026
Remove expired token compat and inert runtime surfaces (Fixes #2535)

Conflict: packages/mcp/src/auth/file-token-store.ts and its test, both
modify/delete (this PR deletes them; HEAD had modified them).

Took the deletion. The only change on the HEAD side came from #3305 making the
MCP package standalone, and it was purely mechanical import rewiring:

  @vybestack/llxprt-code-core/utils/errors.js      -> .../llxprt-code-tools/...
  @vybestack/llxprt-code-core/utils/debugLogger.js -> .../llxprt-code-telemetry/...

That is not a reason to keep a file this PR removes as dead surface. The store
was already superseded by packages/mcp/src/auth/token-storage/, which both
sides keep, and a repository-wide search finds no remaining reference to
file-token-store or FileTokenStore outside the deleted files themselves.

Verified: npm run typecheck clean, no dangling references.

Unrelated observation, left alone deliberately: packages/core/tsconfig.noemit.json
still excludes src/debug/DebugLogger.test.ts and src/policy/policy-engine.test.ts,
which #3243 deleted before any of this. That staleness arrived with #3395's
exclusion list and is a no-op for tsc, so it is not touched here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer:e2e:ok Trusted contributor; maintainer-approved E2E run

Projects

None yet

Development

Successfully merging this pull request may close these issues.

npm run lint needs a 12 GB heap, which puts local linting out of reach for many contributors

1 participant