fix(install): skip linking into .bit_roots when rootComponents is disabled#10356
Merged
zkochan merged 4 commits intoMay 12, 2026
Merged
Conversation
…abled The post-install path (linkCodemods → linkToNodeModulesByIds → NodeModuleLinker.link) called linkPkgsToRootComponents unconditionally, even when the workspace had `rootComponents` set to `false`. The sibling call site in install.main.runtime.ts (`_linkAllComponentsToBitRoots`) is already gated on `hasRootComponents()`; this aligns the linker with that. When a user toggles `rootComponents` from `true` to `false` and reinstalls, the previous install's `.bit_roots/<env>` tree is no longer managed by pnpm, so its internal layout can be inconsistent (e.g. ancestors of `<env>/node_modules/<pkg>` may have been reshaped). Hard-linking into that stale tree was failing with `ENOTDIR` / `ENOENT` and aborting the install. Expose `hasRootComponents()` on `Workspace` (delegates to `dependencyResolver`) so the linker can check it without reaching into the private dep resolver.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR prevents post-install linking from writing into node_modules/.bit_roots when the workspace is configured with rootComponents: false, avoiding install failures caused by stale/unmanaged .bit_roots trees after toggling the setting off.
Changes:
- Added
Workspace.hasRootComponents()as a public wrapper aroundDependencyResolverMain.hasRootComponents(). - Updated
NodeModuleLinker.link()to calllinkPkgsToRootComponents()only whenworkspace.hasRootComponents()is true.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| scopes/workspace/workspace/workspace.ts | Exposes a public hasRootComponents() helper for consumers that shouldn’t reach into the private dependency-resolver field. |
| scopes/workspace/modules/node-modules-linker/node-modules-linker.ts | Skips linking packages into .bit_roots when root components are disabled, preventing errors from stale .bit_roots structures. |
|
|
||
| /** | ||
| * Whether the workspace is configured to use root components (the `.bit_roots` | ||
| * per-env install layout under `node_modules`). |
Member
Author
There was a problem hiding this comment.
Good catch — updated the JSDoc to mention the configurable location (745f572).
GiladShoham
approved these changes
May 8, 2026
GiladShoham
approved these changes
May 8, 2026
| /** | ||
| * Whether the workspace is configured to use root components — the per-env install | ||
| * layout written to `rootComponentsPath` (defaults to `node_modules/.bit_roots`, | ||
| * relocatable via `dependencyResolver.rootComponentsDirectory`). |
Comment on lines
+73
to
+86
| // Only update `.bit_roots` when root components are actually in use. Otherwise | ||
| // we'd be hard-linking into a stale tree left over from a previous install where | ||
| // rootComponents was enabled — pnpm no longer manages that subtree, so its layout | ||
| // can be inconsistent and `mkdir` may throw ENOTDIR/ENOENT through a broken | ||
| // ancestor inside `.bit_roots/<env>/node_modules/...`. | ||
| if (this.workspace.hasRootComponents()) { | ||
| await linkPkgsToRootComponents( | ||
| { | ||
| rootComponentsPath: this.workspace.rootComponentsPath, | ||
| workspacePath, | ||
| }, | ||
| this.components.map((comp) => componentIdToPackageName(comp.state._consumer)) | ||
| ); | ||
| } |
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
The post-install path runs through
linkCodemods→linkToNodeModulesByIds→NodeModuleLinker.link(), and that ended with an unconditional call tolinkPkgsToRootComponentsatnode-modules-linker.ts:73. The sibling call site ininstall.main.runtime.ts(_linkAllComponentsToBitRoots, line 1272) is already gated ondependencyResolver.hasRootComponents()— this aligns the linker with that.When this matters
A user toggles
rootComponentsfromtruetofalseand runsbit install:rootComponents: true) populatednode_modules/.bit_roots/<env>/node_modules/....rootComponents: false): pnpm no longer treats.bit_roots/<env>as a workspace project, and_updateRootDirsis skipped, so that subtree is no longer managed by anyone. Its internal layout can drift — e.g. ancestors of<env>/node_modules/<pkg>may have been reshaped, leaving a regular file or a broken link where a directory used to be.NodeModuleLinker.link()then tries to hard-link the workspace'snode_modules/<pkg>into that stale tree, andmkdir(... { recursive: true })throwsENOTDIR(orENOENTthrough a broken symlink). The whole install aborts.Concrete report from a user:
Changes
Workspace.hasRootComponents()— small public method that delegates to the privatedependencyResolver.hasRootComponents(). The linker (and any other consumer ofWorkspace) can now check this without reaching into the private resolver.NodeModuleLinker.link()— gates thelinkPkgsToRootComponentscall onworkspace.hasRootComponents(). When root components are off, the stale.bit_rootstree is left strictly alone.Notes
.bit_rootsin the first place when it shouldn't. The two are complementary — happy to land in either order..bit_rootswhen the user toggles tofalse. The stale tree is wasted disk but no longer actively harmful once we stop writing to it. If you want bit to also remove it on toggle, that's a separate change worth thinking through (someone may have tooling that reads from there independently).Test plan
npm run lint— same 38 pre-existing TS errors as master (all in unrelated@pnpm/*imports), no new errors in the changed filese2e/harmony/root-components.e2e.tsis the natural home for a "toggle off and reinstall" scenario but I held off on adding one in this draft; let me know if you want it bundled in.