[rush-cli-client] Connect to a warm daemon without loading @microsoft/rush-lib - #6090
Merged
Sean Larkin (TheLarkInn) merged 3 commits intoSep 24, 2026
Merged
Conversation
…/rush-lib Fixes #6054 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The lazy-loading paths preserve existing daemon behavior and are covered by targeted startup, replacement, fallback, and integration tests.
Review effort: Balanced
Findings: None
What changed in this PR
Optimizes warm daemon connections by deferring expensive Rush module loading until startup, fallback, version selection, or rushx execution requires it.
Changes:
- Adds lazy daemon start-command resolution to client core.
- Lazily loads Rush libraries and enables Node’s compile cache.
- Adds startup-budget and daemon-selection coverage.
| File | Description |
|---|---|
libraries/rush-client-core/src/connectOrStartDaemon.ts |
Adds lazy start-command resolution. |
libraries/rush-client-core/src/test/connectOrStartDaemon.test.ts |
Tests cold, warm, and replacement behavior. |
common/reviews/api/rush-client-core.api.md |
Updates the public API report. |
common/changes/@rushstack/rush-client-core/client-startup-perf_2026-09-24-03-05.json |
Records the core patch. |
common/changes/@rushstack/rush-cli-client/client-startup-perf_2026-09-24-03-05.json |
Records the CLI optimization. |
apps/rush-cli-client/src/test/StartupModuleProbe.ts |
Reports startup module loading. |
apps/rush-cli-client/src/test/startupBudget.test.ts |
Enforces the warm-start module budget. |
apps/rush-cli-client/src/test/NativeBuildTestFixture.ts |
Supports Node preload arguments. |
apps/rush-cli-client/src/test/daemonConnectionSelection.test.ts |
Covers lazy launcher selection. |
apps/rush-cli-client/src/routing.ts |
Defers RushXCommand loading. |
apps/rush-cli-client/src/lazyRushModules.ts |
Centralizes lazy Rush imports. |
apps/rush-cli-client/src/launchClient.ts |
Removes eager Rush entry-point loading. |
apps/rush-cli-client/src/daemonConnectionOptions.ts |
Lazily creates bundled start commands. |
apps/rush-cli-client/bin/rush-client |
Enables compile caching when available. |
apps/rush-cli-client/bin/rushx-client |
Enables compile caching when available. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Mo Jazayeri (mojaza)
approved these changes
Sep 24, 2026
Sean Larkin (TheLarkInn)
deleted the
thelarkinn-fix-client-startup-perf
branch
September 24, 2026 18:27
Sean Larkin (TheLarkInn)
added a commit
that referenced
this pull request
Sep 24, 2026
…sh-daemon main (#6090) now keeps rush-client from loading @microsoft/rush-lib with on-demand requires and lazy start-command resolution. Take that implementation for the client and drop this branch's overlapping client-only modules (RushJsonLocation, RushXCommandLineArguments, DaemonLaunchCommand and bundledRushVersion) and their change files. The daemon-side changes in this branch are unaffected. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.
Summary
Before connecting to the daemon, every
rush-client/rushx-clientrunrequire()d all of@microsoft/rush-lib(about 600 ms, ~1360 modules). With this change the warm connect path never evaluates the rush-lib entry point. Heavy modules now load only on the paths that need them: in-process fallback, rushx discovery, daemon auto-start/replacement, and Rush version selection.Root cause
launchClient.tsimportedRush,RushConfiguration,daemonEnvironmentVariablesandresolveDaemonConfigurationfrom the rush-lib index. It also importedMinimalRushConfigurationandVersionSelectedDaemonLauncherat the top level, and the launcher itself imports rush-lib.routing.tsimportedRushXCommandfrom rush-lib even forrushcommands.daemonConnectionOptions.tsimportedRush(only for.version). On every auto-start-enabled invocation it also computed the daemon start command eagerly throughVersionSelectedDaemonLauncher, although that command is only needed when no compatible daemon is running.Fix
@rushstack/rush-client-core: new optionalIConnectOrStartDaemonOptions.resolveStartCommandAsync().connectOrStartDaemonAsynccalls it only after the first connect attempt fails, i.e. when it has to start or replace a daemon. A version mismatch with a resolver present is treated the same as one with astartCommand, so the start/replace semantics do not change. The resolver survives the{ ...connection }spread inexecuteWithDaemonRestartAsync, so the restart path still starts a successor. (API report updated.)@rushstack/rush-cli-client:lazyRushModules.ts. It loads rush-lib,MinimalRushConfigurationandVersionSelectedDaemonLauncheron demand. It also provides atryFindRushJsonLocationwith the same search asRushConfiguration.tryFindRushJsonLocation, and agetBundledRushVersion()(= Rush.version) that reads rush-lib'spackage.json.launchClient.tsgetsresolveDaemonConfiguration/daemonEnvironmentVariablesfrom the deep import@microsoft/rush-lib/lib/api/DaemonConfiguration, which evaluates only that module.MinimalRushConfigurationis loaded only for rushx. TheDaemonLauncherUnavailableErrorclass is loaded only in the error path, and only when the error is not aDaemonClientError.daemonConnectionOptions.ts: when the selected version is the bundled one, it returnsresolveStartCommandAsyncinstead of an eagerstartCommand. The installation-metadata check still runs eagerly, so the synchronous-launcher error is unchanged. Version-selected launches (a differentrushVersion) are unchanged.routing.ts:RushXCommandis loaded only for rushx.bin/rush-clientandbin/rushx-clientcallrequire('node:module').enableCompileCache?.()(a no-op below Node 22.1). I verified that it does not setNODE_COMPILE_CACHEfor child processes, so the daemon environment fingerprint is unaffected (board Fix Rush build break. #205).Compatibility with #6082
#6082 (agent reporter) rewrites
start.tsand adds lines tolaunchClient.tsandrouting.ts. This PR doesn't touchstart.ts. I placed the new imports so they don't overlap #6082's hunks. The only textual overlap is thecatchblock'sinstanceof DaemonLauncherUnavailableErrorcheck, where #6082 addsagentRenderer?.dispose()on the next line. Resolving that means keeping both changes. #6082's new modules (AgentProgressRenderer,outputSelection) are already rush-lib-free, so the combined start path stays lean.Tests
apps/rush-cli-client/src/test/startupBudget.test.ts, the startup-budget test:buildanddaemon statusunder a--requiremodule probe.lib-commonjs/index.js),@microsoft/rush'sstart/MinimalRushConfiguration, or rush-daemon'sindex/VersionSelectedDaemonLauncher, and that the warm run stays within 600 modules (it measures about 310).--no-daemonpath, so the test can fail when it should.daemonConnectionSelection.test.tsnow covers the lazy start command.Linux validation (WSL Ubuntu-24.04, Node 22.23.2)
Commands:
node common/scripts/install-run-rush.js build --to @rushstack/rush-cli-client. Passed.heft test --test-path-pattern "startupBudget|daemonConnectionSelection|versionSelectionFallback". Passed.bench --runs 12 --warmup 2, interleaved, on the same warm daemon in a 12-project synthetic workspace. "before" is the unfixed toolchainrush-client. Load average was 18 to 10 during the run.daemon statusdaemon status, without the compile cachebuild --to p05require.cache(warm build)node -e 0Behavior checks with the fixed client:
--no-daemon build --to p01(in-process fallback): exit 0.daemon stop, thenbuild --to p01: cold auto-start works through the lazy resolver, exit 0, same collated output.daemon status/daemon stopafterwards: normal.rush test --only @rushstack/rush-cli-client --only @rushstack/rush-client-core: passed (all suites, 59 s / 37 s).Optional follow-ups
@rushstack/node-core-library's index (~90 ms) is still pulled in byJsonFile/LockFileimports in the client, rush-daemon'sDaemonInstallation, and rush-client-core'sStartupLock.DaemonConfigurationimport still makes the rush-lib webpack runtime compilecommons.js(without evaluating it). MovingDaemonConfigurationinto a tiny standalone module would remove that too.Fixes #6054
This came out of the automated rushd Linux performance/behavior analysis ("Rushd Hive", board threads #26 / #196 / #205).