Skip to content

[BUG] npm install -g crashes: Cannot read properties of null (reading 'children') when a peer set resolves across multiple prerelease lines #9911

Description

@mudrii

Is there an existing issue for this?

  • I have searched the existing issues

This issue exists in the latest npm version

  • I am using the latest npm

This is not just a request to bump a dependency for a CVE

  • This is not solely a request to bump a dependency for a CVE

Current Behavior

npm install -g of a package whose peer group resolves across multiple prerelease lines crashes with a TypeError from Arborist instead of installing (or reporting a peer conflict):

$ npm install -g --prefix /tmp/npm-repro @deepseek-harness-tui/dsh-tui@0.9.0
npm error Cannot read properties of null (reading 'children')

Verbose stack (npm 12.0.2 / @npmcli/arborist 10.0.2):

501 verbose stack TypeError: Cannot read properties of null (reading 'children')
501 verbose stack     at new PlaceDep (.../node_modules/npm/node_modules/@npmcli/arborist/lib/place-dep.js:306:32)
501 verbose stack     at #buildDepStep (.../node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js:1145:18)
501 verbose stack     at async Arborist.buildIdealTree (.../node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/build-ideal-tree.js:188:7)
501 verbose stack     at async Arborist.reify (.../node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/reify.js:118:5)
501 verbose stack     at async Install.exec (.../node_modules/npm/node_modules/npm/lib/commands/install.js:176:5)

On npm 11.19.0 the same crash occurs at the same lines in arborist 9.x.

Expected Behavior

No crash. If the peer set cannot be placed, npm should either complete the install (placing the conflicting peers deeper) or emit a proper ERESOLVE peer-conflict error with the usual --force/--legacy-peer-deps guidance.

Steps To Reproduce

  1. Use npm 11.19.0 or 12.0.2 (latest). No project needed; an empty global prefix is enough:
    mkdir -p /tmp/npm-repro/lib/node_modules
    npm install -g --prefix /tmp/npm-repro @deepseek-harness-tui/dsh-tui@0.9.0
  2. Observe the TypeError: Cannot read properties of null (reading 'children') crash. Nothing gets installed.

Notes on the trigger: @deepseek-harness-tui/dsh-tui@0.9.0 declares 26 required peerDependencies with dual ranges such as ^0.1.0-rc.6 || ^0.1.1-rc.1, while its regular dependencies pin ^0.1.0-rc.8 of the same packages and their dist-tags point at older lines (latest = 0.1.0-rc.6, next = 0.1.1-rc.2). Mixing those prerelease lines inside one peer set is what trips the bug. The previous release 0.8.6, whose peers use single ranges (^0.1.0-rc.7), installs fine on the same npm versions, so the user-visible trigger is package shape, not user error.

Environment

  • npm 12.0.2 (also reproduced on npm 11.19.0; also unfixed on the latest branch as of today — place-dep.js still has the unguarded virtualRoot.children at the same spot)
  • Node.js v24.19.0 (also v24.14.1)
  • macOS 15 (Darwin 25.6.0); no OS-specific code is involved
  • Commands: npm install -g (crash also happens with --dry-run, --force, and --install-strategy=nested)

Anything else?

Root-cause analysis (happy to PR a fix if the direction below sounds right):

  1. #loadPeerSet in build-ideal-tree.js already knows a node can be replaced and detached mid-loop; it guards with if (!node.parent) break (and the comment above it says a detached node needs no further processing) — but it then still returns that detached node.
  2. #buildDepStep passes the returned (detached, parent === null) node to new PlaceDep({ edge, dep, ... }).
  3. In place-dep.js, the PlaceDep constructor stores const virtualRoot = this.dep.parent (line 242 in npm 12.0.2) and later does virtualRoot.children.get(peerEdge.name) when placing the node's unmet peers (line 306) — null deref.

Possible fixes: have #loadPeerSet return null (or the replacement node) when detached and skip it in #buildDepStep, or add a null guard in PlaceDep around the peer-placement loop (treat missing virtualRoot as "no peers to place here"). The #loadPeerSet detachment comments mention npm/cli#9249 era fixes, so there may be prior art in how detached nodes are handled elsewhere.

Possibly related: #9787 (Cannot read properties of null (reading 'edgesOut') inside #loadPeerSet) looks like the same underlying "detached node escapes peer-set resolution" problem crashing at a different line; it is currently labeled Cannot Reproduce, and the deterministic repro above may help there too.

Workarounds I found, for other users hitting this: pinning every peer to one release line in the same npm i -g command works (e.g. all @deepseek-ai/dsh-* peers at 0.1.0-rc.8). --legacy-peer-deps avoids the crash but skips installing the peers entirely, which breaks this package at runtime since it imports them.

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