Skip to content

[BUG] npm install --workspace crashes with install-strategy=linked #9038

@manzoorwanijk

Description

@manzoorwanijk
  • 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 strategy
  • npm ci works fine with linked strategy
  • npm install --workspace=<ws> works fine with install-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

  1. 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
  1. Install normally first (this works):
npm install
  1. Now try to add a package to a workspace:
npm install --workspace=@test/foo lodash@4.17.21
  1. Observe the crash. Even a no-op workspace install crashes:
npm install --workspace=@test/foo

Root 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=linked

Context

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions