Findings from profiling bd install in the bit2 workspace (331 components, ~8.5k packages) with the Rust pnpm engine (@pnpm/napi). Every install was taking ~4 minutes even with an up-to-date lockfile, and rm -rf node_modules && bd install appeared to hang at Progress: resolved 0, reused N. The engine-side causes are being fixed in pnpm (see references at the bottom); this issue tracks the two findings that need fixes in Bit, because they force the engine into a full re-resolution on every install regardless of how fast the engine is.
1. Generated component manifests differ depending on whether node_modules exists
Bit's dependency detection produces different manifests for the same workspace state depending on the presence of node_modules:
- with
node_modules present, the generated manifests for ui/api-diff-view, ui/hooks/use-bulk-paged-query, and ui/inline-config-compare include @apollo/client@^3.12.0;
- with
node_modules absent, the same components' manifests do not include it — even though all three genuinely import/require @apollo/client in their source.
The engine's lockfile freshness check therefore flaps forever. Captured from the engine's staleness log (TRACE=pacquet::install=info):
- install with
node_modules present, lockfile from a no-node_modules install: specifiers in the lockfile don't match specifiers in package.json: * 1 dependency was added: @apollo/client@^3.12.0
- install after
rm -rf node_modules, lockfile from a with-node_modules install: ... * 1 dependency was removed: @apollo/client@^3.12.0
Because no lockfile can satisfy both states, every install after removing node_modules (and the next one after it is restored) runs a full re-resolution (~3–4 minutes through the engine today) instead of the frozen path. With the flap fixed, the measured frozen path is ~1s of engine time on a warm workspace and ~5.5s of engine materialization after rm -rf node_modules — the "hang" at resolved 0, reused N was the full re-resolution, not the store/link pipeline.
Expected fix: dependency detection / version attribution must be independent of installed state — a component that imports @apollo/client should get the same manifest entry whether or not node_modules exists.
2. lynx.js does not pass dedupePeers: true to the engine
Bit's existing lockfiles record settings.dedupePeers: true (generated by the previous engine integration). The current installOptions built in scopes/dependencies/pnpm/lynx.ts does not pass dedupePeers, so the engine resolves it to its default (false) and its freshness gate reports:
`dedupePeers` in the lockfile (true) doesn't match the current config (false)
→ every install of an existing Bit repo is treated as outdated, full-resolves, and rewrites the entire lockfile's peer suffixes (a ~50k-line diff), which then keeps churning.
Until now this was not fixable from Bit's side because the @pnpm/napi install options did not expose dedupePeers at all; that binding gap is fixed in pnpm/pnpm#13492. Once the @pnpm/napi release containing it ships, lynx.ts should pass dedupePeers: true alongside dedupePeerDependents: true.
Engine-side context (fixed or in progress on the pnpm side)
For completeness — these were found in the same investigation and are not Bit bugs, but they explain the remaining numbers:
With both Bit-side fixes in place, bd install only pays resolution when dependencies actually change; warm repeat installs take ~1s of engine time and rm -rf node_modules && bd install ~5.5s.
Written by an agent (Claude Code, claude-fable-5), investigating together with Zoltan Kochan.
Findings from profiling
bd installin thebit2workspace (331 components, ~8.5k packages) with the Rust pnpm engine (@pnpm/napi). Every install was taking ~4 minutes even with an up-to-date lockfile, andrm -rf node_modules && bd installappeared to hang atProgress: resolved 0, reused N. The engine-side causes are being fixed in pnpm (see references at the bottom); this issue tracks the two findings that need fixes in Bit, because they force the engine into a full re-resolution on every install regardless of how fast the engine is.1. Generated component manifests differ depending on whether
node_modulesexistsBit's dependency detection produces different manifests for the same workspace state depending on the presence of
node_modules:node_modulespresent, the generated manifests forui/api-diff-view,ui/hooks/use-bulk-paged-query, andui/inline-config-compareinclude@apollo/client@^3.12.0;node_modulesabsent, the same components' manifests do not include it — even though all three genuinelyimport/require@apollo/clientin their source.The engine's lockfile freshness check therefore flaps forever. Captured from the engine's staleness log (
TRACE=pacquet::install=info):node_modulespresent, lockfile from a no-node_modulesinstall:specifiers in the lockfile don't match specifiers in package.json: * 1 dependency was added: @apollo/client@^3.12.0rm -rf node_modules, lockfile from a with-node_modulesinstall:... * 1 dependency was removed: @apollo/client@^3.12.0Because no lockfile can satisfy both states, every install after removing
node_modules(and the next one after it is restored) runs a full re-resolution (~3–4 minutes through the engine today) instead of the frozen path. With the flap fixed, the measured frozen path is ~1s of engine time on a warm workspace and ~5.5s of engine materialization afterrm -rf node_modules— the "hang" atresolved 0, reused Nwas the full re-resolution, not the store/link pipeline.Expected fix: dependency detection / version attribution must be independent of installed state — a component that imports
@apollo/clientshould get the same manifest entry whether or notnode_modulesexists.2.
lynx.jsdoes not passdedupePeers: trueto the engineBit's existing lockfiles record
settings.dedupePeers: true(generated by the previous engine integration). The currentinstallOptionsbuilt inscopes/dependencies/pnpm/lynx.tsdoes not passdedupePeers, so the engine resolves it to its default (false) and its freshness gate reports:→ every install of an existing Bit repo is treated as outdated, full-resolves, and rewrites the entire lockfile's peer suffixes (a ~50k-line diff), which then keeps churning.
Until now this was not fixable from Bit's side because the
@pnpm/napiinstall options did not exposededupePeersat all; that binding gap is fixed in pnpm/pnpm#13492. Once the@pnpm/napirelease containing it ships,lynx.tsshould passdedupePeers: truealongsidededupePeerDependents: true.Engine-side context (fixed or in progress on the pnpm side)
For completeness — these were found in the same investigation and are not Bit bugs, but they explain the remaining numbers:
readPackagehook dispatch cost ~1 event-loop tick per manifest (~200s per full resolve at Bit's scale) — now batched; a quadratic per-package tree scan in the resolver — now indexed;overrideswere recorded in random order every install — now order-preserving; all in fix(napi): deterministic overrides, hook-free install options, and large-workspace install fixes pnpm/pnpm#13492;With both Bit-side fixes in place,
bd installonly pays resolution when dependencies actually change; warm repeat installs take ~1s of engine time andrm -rf node_modules && bd install~5.5s.Written by an agent (Claude Code, claude-fable-5), investigating together with Zoltan Kochan.