Is there an existing issue for this?
Related: #9835 (npm's own remediation prompt suggests the exact command that crashes), #6006 (same error message, different trigger)
This issue exists in the latest npm version
Current Behavior
npm install -g --allow-scripts=<pkg> with no positional package spec always crashes with Cannot destructure property 'name' of '.for' as it is undefined. — including the exact command npm itself prints in the install-scripts remediation warning:
npm warn install-scripts Run `npm install -g --allow-scripts=ffmpeg-static` to allow these scripts once,
or `npm config set allow-scripts=ffmpeg-static --location=user` to allow them for all global installs.
This makes the documented one-off escape hatch for a blocked install script unusable, and the npm config set --location=user alternative is a permanent policy change for a one-time need.
npm error Cannot destructure property 'name' of '.for' as it is undefined.
npm error A complete log of this run can be found in: ~/.npm/_logs/...-debug-0.log
verbose stack TypeError: Cannot destructure property 'name' of '.for' as it is undefined.
verbose stack at [diffTrees] (.../@npmcli/arborist/lib/arborist/reify.js:454:20)
verbose stack at Arborist.reify (.../@npmcli/arborist/lib/arborist/reify.js:138:29)
verbose stack at async Install.exec (.../npm/lib/commands/install.js:176:5)
verbose stack at async Npm.exec (.../npm/lib/npm.js:194:9)
Expected Behavior
Either the install should succeed, or install.js should not feed a synthetic . spec into Arborist in this case. Failing with a TypeError is never acceptable.
Steps To Reproduce
Deterministic, and not specific to any package:
$ npm install -g --allow-scripts=left-pad
npm error Cannot destructure property 'name' of '.for' as it is undefined.
$ npm install -g --allow-scripts=left-pad left-pad # works
added 1 package in 249ms
The same holds for the space-separated form (--allow-scripts left-pad), and for any package name.
Root cause
install.js substitutes a . spec when a global install has no positional args, so that npm reifies the global tree:
// npm/lib/commands/install.js:138
if (isGlobalInstall && !args.length) {
args = ['.']
}
That spec is passed to Arborist as add, and ends up in _resolvedAdd. The global tree root has no dependency entry for it, so the edgesOut lookup misses and undefined is added to #explicitRequests:
// @npmcli/arborist/lib/arborist/build-ideal-tree.js:651
for (const spec of this[_resolvedAdd]) {
if (spec.tree === tree) {
this.#explicitRequests.add(tree.edgesOut.get(spec.name)) // <-- undefined
}
}
diffTrees then destructures it under the global branch:
// @npmcli/arborist/lib/arborist/reify.js:449
if (this.options.global && this.explicitRequests.size) {
...
for (const { name } of this.explicitRequests) { // <-- TypeError
Confirmed by instrumenting Set.prototype.add during the crash; the undefined originates at build-ideal-tree.js:653.
Passing a real spec masks the bug, because that spec does get an edge in edgesOut — which is also why the workaround below works.
Suggested fix
Guard the lookup in build-ideal-tree.js:651 (and the globalExplicitUpdateNames loop right below it, which has the same shape):
for (const spec of this[_resolvedAdd]) {
if (spec.tree === tree) {
const edge = tree.edgesOut.get(spec.name)
if (edge) {
this.#explicitRequests.add(edge)
}
}
}
Workaround
Repeat the package as a positional spec:
npm install -g --allow-scripts=ffmpeg-static ffmpeg-static
Note that a project .npmrc allow-scripts is not consulted for global installs, so the npm config set ... --location=user route is the only other option, and it is global/permanent.
Environment
- npm -v: 12.0.2
- node -v: v26.8.1
- OS: Arch Linux, kernel 7.2.7-arch1-1
- Prefix: asdf-installed nodejs 26.8.1
Is there an existing issue for this?
Related: #9835 (npm's own remediation prompt suggests the exact command that crashes), #6006 (same error message, different trigger)
This issue exists in the latest npm version
Current Behavior
npm install -g --allow-scripts=<pkg>with no positional package spec always crashes withCannot destructure property 'name' of '.for' as it is undefined.— including the exact command npm itself prints in theinstall-scriptsremediation warning:This makes the documented one-off escape hatch for a blocked install script unusable, and the
npm config set --location=useralternative is a permanent policy change for a one-time need.Expected Behavior
Either the install should succeed, or
install.jsshould not feed a synthetic.spec into Arborist in this case. Failing with aTypeErroris never acceptable.Steps To Reproduce
Deterministic, and not specific to any package:
The same holds for the space-separated form (
--allow-scripts left-pad), and for any package name.Root cause
install.jssubstitutes a.spec when a global install has no positional args, so that npm reifies the global tree:That spec is passed to Arborist as
add, and ends up in_resolvedAdd. The global tree root has no dependency entry for it, so theedgesOutlookup misses andundefinedis added to#explicitRequests:diffTreesthen destructures it under the global branch:Confirmed by instrumenting
Set.prototype.addduring the crash; theundefinedoriginates atbuild-ideal-tree.js:653.Passing a real spec masks the bug, because that spec does get an edge in
edgesOut— which is also why the workaround below works.Suggested fix
Guard the lookup in
build-ideal-tree.js:651(and theglobalExplicitUpdateNamesloop right below it, which has the same shape):Workaround
Repeat the package as a positional spec:
npm install -g --allow-scripts=ffmpeg-static ffmpeg-staticNote that a project
.npmrcallow-scriptsis not consulted for global installs, so thenpm config set ... --location=userroute is the only other option, and it is global/permanent.Environment