-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
- I have searched the existing issues
- I am using the latest npm
Current Behavior
Running npm install with both --workspace and install-strategy=linked crashes during reify.
npm 11.x:
TypeError: Cannot read properties of null (reading 'package')
at set root (node_modules/@npmcli/arborist/lib/node.js:797:35)
at set root (node_modules/@npmcli/arborist/lib/node.js:863:20)
at set root (node_modules/@npmcli/arborist/lib/node.js:863:20)
at Arborist.reify (node_modules/@npmcli/arborist/lib/arborist/reify.js:205:21)
npm 10.x:
TypeError: this.idealTree.children.get is not a function
at Arborist.[diffTrees] (node_modules/@npmcli/arborist/lib/arborist/reify.js:441:45)
Notably:
npm install(without--workspace) works fine with linked strategynpm ciworks fine with linked strategynpm install --workspace=<ws>works fine withinstall-strategy=hoisted(default)
Expected Behavior
npm install --workspace=<ws> <pkg> should work the same way with linked strategy as it does with hoisted.
Steps To Reproduce
- Create a minimal workspace project:
mkdir /tmp/test-linked-ws && cd /tmp/test-linked-ws
# Root package.json
cat > package.json << 'EOF'
{
"name": "test-linked-ws",
"private": true,
"workspaces": ["packages/*"]
}
EOF
# .npmrc
echo "install-strategy=linked" > .npmrc
# Workspace package
mkdir -p packages/foo
cat > packages/foo/package.json << 'EOF'
{
"name": "@test/foo",
"version": "1.0.0",
"dependencies": {
"abbrev": "^2.0.0"
}
}
EOF- Install normally first (this works):
npm install- Now try to add a package to a workspace:
npm install --workspace=@test/foo lodash@4.17.21- Observe the crash. Even a no-op workspace install crashes:
npm install --workspace=@test/fooRoot Cause Analysis
The linked strategy's _createIsolatedTree() in isolated-reifier.js constructs a plain-object tree (not actual Node/Link instances). This tree has key structural differences from a normal ideal tree:
1. children is an Array, not a Map
In isolated-reifier.js line 289:
const root = {
// ...
children: [], // Array, not Map
}Standard Node instances use a Map for children. The workspace-filtering code in reify.js depends on Map.get():
npm 10 - reify.js:441 (crashes directly):
const ideal = this.idealTree.children.get(ws)
// ^^^ Array has no .get()npm 11 - reify.js:425 (defensive check added, avoids this crash):
const ideal = this.idealTree.children.get && this.idealTree.children.get(ws)2. inventory.query() is a stub
In isolated-reifier.js line 294:
root.inventory.query = () => {
return []
}The real Inventory class provides query(key, value) to look up nodes by realpath, name, etc. The stub always returns [], meaning set root on actual Node instances can never find matching targets in the isolated tree.
3. Re-rooting actual nodes into the isolated tree crashes (npm 11)
On npm 11, since the defensive .get && check causes workspace ideal nodes to be undefined, the filterNodes array only contains nodes from actualTree. When reify.js:205 runs:
actual.root = this.idealTree // this.idealTree is the isolated tree (plain object)This triggers the set root setter on real Node instances (node.js:797). The setter cascades through node.js:863 (re-rooting family members), and eventually a Link node tries to access target.package where target is null - because the inventory.query stub returned [], so no target could be resolved.
Summary
The isolated tree (plain objects) and workspace filtering (expects real Node/Link instances with Map children and proper Inventory) are fundamentally incompatible. The _createIsolatedTree was designed for full npm install, not for filtered workspace installs.
Environment
- npm: 11.11.0 (also reproduced on 10.9.3)
- Node.js: 22.20.0
- OS Name: macOS (Darwin 25.3.0)
- System Model Name: Macbook Pro
- npm config:
install-strategy=linkedContext
We're testing the linked install strategy on the Gutenberg monorepo (~200 workspace packages) in PR #75814. Full npm install and npm ci work correctly. The crash only manifests when using --workspace to install/add packages to individual workspaces, which is a common workflow in monorepos.