[BUG] npm install -g crashes: Cannot read properties of null (reading 'children') when a peer set resolves across multiple prerelease lines
Nobody has claimed this yet.
- 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
- 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 - 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
latestbranch as of today —place-dep.jsstill has the unguardedvirtualRoot.childrenat 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):
#loadPeerSetinbuild-ideal-tree.jsalready knows a node can be replaced and detached mid-loop; it guards withif (!node.parent) break(and the comment above it says a detached node needs no further processing) — but it then still returns that detached node.#buildDepSteppasses the returned (detached,parent === null) node tonew PlaceDep({ edge, dep, ... }).- In
place-dep.js, thePlaceDepconstructor storesconst virtualRoot = this.dep.parent(line 242 in npm 12.0.2) and later doesvirtualRoot.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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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