Skip to content

[rush] rush-cli-client: every invocation loads all of @microsoft/rush-lib (~600 ms) before connecting to the daemon #6054

Closed

Description

Summary

With a warm daemon, rush-client daemon status takes 766 ms (median of 20 runs), while node -e 0 takes 40 ms and the socket connect + hello + pong takes about 7 ms. About 600 ms is spent require-ing @microsoft/rush-lib (its webpack bundle plus about 1,263 external modules) before the socket is opened. The same overhead applies to every warm rush-client build, and it dominates the IPC path, which is fast. A lean prototype that runs the same status call through @rushstack/rush-client-core without rush-lib takes 182 ms.

Repro steps

node -e 0                                   # ~40 ms
node -e "require('@microsoft/rush-lib')"    # ~685 ms
rush-client daemon status                   # ~766 ms with a warm daemon
node -r <timing hook> rush-client build     # socket connect happens at 660-710 ms; connect +1.5 ms; first daemon data +2 ms

Expected result: The client connect path only loads what it needs (transport, protocol, client-core, and a small rush.json lookup), and rush-lib loads lazily only for fallback, rushx, or auto-start.

Actual result: Every invocation pays the full engine load first.

Details

Import costs, measured in fresh processes: @microsoft/rush-lib 574-594 ms (+1,263 modules). @rushstack/rush-client-core alone 90-120 ms (+236 modules), because StartupLock.ts:6 imports LockFile from the @rushstack/node-core-library index (93 ms, 172 modules). Transport + protocol 35 ms (55 modules).

Root cause (main @ 60007c9):

  • apps/rush-cli-client/src/launchClient.ts:3-9: a top-level import { Rush, RushConfiguration, daemonEnvironmentVariables, resolveDaemonConfiguration } from '@microsoft/rush-lib', used only for tryFindRushJsonLocation, Rush.version and resolveDaemonConfiguration.
  • routing.ts:5: imports RushXCommand from rush-lib (needed only for rushx).
  • launchClient.ts:25-26: import @microsoft/rush/lib/MinimalRushConfiguration and @rushstack/rush-daemon/lib/VersionSelectedDaemonLauncher, each of which transitively loads rush-lib. daemonConnectionOptions.ts:6, 10-16 does the same.
  • libraries/rush-client-core/src/StartupLock.ts:6: eager node-core-library index import.

Suggested fix: make the connect path free of rush-lib. Inline the rush.json lookup and version read, deep-import the daemon configuration resolver, and load rush-lib, MinimalRushConfiguration, VersionSelectedDaemonLauncher and RushXCommand lazily only on fallback, rushx, or auto-start paths. Lazy-load LockFile in rush-client-core. Add a startup-budget test (for example, assert that the rush-lib bundle is not in require.cache after a status call). Expected result: daemon status goes from 766 ms to roughly 90-180 ms, and warm no-op builds get about 600 ms faster.

This was found during an automated performance/behavior analysis of rush-client/rushd on Linux and independently reproduced (751 ms median status versus 34 ms for node -e 0).

Standard questions

Question Answer
@microsoft/rush globally installed version? built from main @ 60007c9 (5.179.0)
rushVersion from rush.json? 5.179.0
pnpmVersion, npmVersion, or yarnVersion from rush.json? pnpm@10.27.0
(if pnpm) useWorkspaces from pnpm-config.json? true
Operating system? Linux (WSL2 Ubuntu 24.04)
Would you consider contributing a PR? Yes
Node.js version (node -v)? 22.23.2

Activity

  1. added a commit that references this issue on Sep 24, 2026
    b82e256
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions