npm / npm/cli

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

Open
#9,911 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Bug cmd:install Needs Triage ws:arborist
Dominant language
JavaScript
Stars
10.1k
Forks
4.7k
Avg merge
2d 2h
Merged PRs (30d)
19

Description

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with #loadPeerSet in lib/arborist/build-ideal-tree.js and the peer-placement path in lib/place-dep.js, focusing on how a detached node reaches PlaceDep. Reproduce the failure with the npm install command in the issue, then verify that the result is either a completed install or a normal ERESOLVE error rather than a null dereference. Add a regression test if the relevant test location can be identified from the surrounding Arborist tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
cli, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.