Repository navigation
[BUG] npm install <pkg> on an existing lockfile prunes a nested optional peer and leaves its edge invalid #10086
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
When a package is added or updated against an existing lockfile, and one of its new dependencies has an optional peer that conflicts with a version already hoisted at the root, npm writes a lockfile with an invalid peerOptional edge.
During placement npm does the right thing: it nests the dependency together with a copy of the optional peer that satisfies the range. Then #fixDepFlags prunes that copy. calcDepFlags keeps nodes reachable only through a peerOptional edge extraneous, and #idealTreePrune runs because the tree was loaded from a lockfile and mutated. Afterwards the nested dependency resolves the optional peer to the hoisted version, which does not satisfy it.
Real example: jsdom@30 pulls in @exodus/bytes, which has an optional peer @noble/hashes@"^1.8.0 || ^2.0.0", while selfsigned (via pkijs@3.4.0) needs @noble/hashes@1.4.0 exactly. The silly log shows placeDep node_modules/jsdom @noble/hashes@2.3.0 OK for: @exodus/bytes@1.15.1, but that copy is gone from the saved lockfile:
npm lsreports@noble/hashes@1.4.0 invalid: "^1.8.0 || ^2.0.0" from node_modules/jsdom/node_modules/@exodus/bytes(6 invalid edges).npm ciwith the same npm accepts the lockfile (it trusts invalid peerOptional edges since fix(arborist): fixpeerOptionaldependency resolution inbuildIdealTree#8981).- npm 10
npm cirejects it withMissing: @noble/hashes@2.4.0 from lock file.
A fresh install of the same package.json without a lockfile skips the prune and ends up valid. #4859 (fixed in 11.10.1) covered the fresh-install path only. #9249 comes from the same prune of extraneous optional peers; there the edge ends up missing, here it ends up pointing at a version that does not satisfy it.
Expected Behavior
The nested copy of the optional peer stays, so the lockfile is valid and npm ls reports nothing invalid.
Steps To Reproduce
--before pins pkijs to 3.4.0, which has the exact @noble/hashes@1.4.0 dependency:
mkdir repro && cd repro
echo '{"name":"r","private":true,"devDependencies":{"jsdom":"26.1.0","selfsigned":"5.5.0"}}' > package.json
O="--package-lock-only --no-audit --no-fund --ignore-scripts --before=2026-08-15"
npm install $O
npm install $O --save-exact jsdom@30.0.1
npm ls --package-lock-only @noble/hashes # invalid edges, ELSPROBLEMS
npm ci --ignore-scripts # passes
rm -rf node_modules && npx -y npm@10 ci --ignore-scripts # Missing: @noble/hashes@2.4.0 from lock fileEnvironment
- npm: 12.1.0 and 11.20.0, also current
main(b317f16) - Node.js: 26.0.0
- OS Name: macOS 26.5
- System Model Name: MacBook Pro (Apple Silicon)
- npm config:
; defaults, no .npmrc
Activity