One warning line, npm warn install-scripts 1 package had install scripts blocked because they are not covered by allowScripts, covers four different matching failures: a local file: spec your key does not spell the way Arborist stored it, a relative key resolved against the wrong root, store paths written into the manifest by install-strategy=linked, and an allow-scripts config value exported into the lifecycle environment. Three were fixed in npm 12.1.0 and 12.2.1 (npm/cli #9914, #9913, #9941); the npx leak in npm/cli #9968 is still open. Before changing a character of the allowScripts object in package.json, print the specs npm recorded for every package that has a script.

On a patch day last month I checked a defensive claim against my own server before passing it on, running the decoder probe from Node's own test/parallel/test-zlib-invalid-input.js instead of reading node -v. It returned errno=-9 on an EOL Node 20 box: unpatched, read out of the running runtime rather than inferred from a version table. A committed allowScripts key is a claim of the same kind. Whether the script executed is a question about a file on disk.

Why does npm 12 still block a script that is in allowScripts?

  1. Prove the script ran by reading the file it writes. The npm 12.0.0 breaking-change entry in the v12 changelog tells you to record approvals with npm install-scripts approve and then run npm rebuild, because approval never retro-runs anything. A clean exit code only states that the manifest covered what the install asked for, the condition ESTRICTALLOWSCRIPTS fails the gate on. Read the binary in the same shell:
npm rebuild esbuild --foreground-scripts && ls node_modules/esbuild/bin/esbuild
install-scripts blocked warningIs the dependency from the registry?Key must equal the file: fetchSpec form; needs npm 12.1.0Is install-strategy set to linked?Key was written into node_modules/.store; needs npm 12.2.1Did you get EALLOWSCRIPTS instead of a warning?allow-scripts leaked from a user .npmrc or the npx environmentPinned key no longer matches the installed version"no, file: or link""yes""yes""no""yes""no"
  1. Copy the key out of package-lock.json, character for character. Arborist matches local sources against the file: form that consistentResolve() produces, not the dependency range you hand-typed, which is how electron-winstaller@5.3.0 and several hand-written file: variants all failed for the reporter of npm/cli #9900 on npm 12.0.2. Print the real spec next to every package that declares a script:
jq -r '.packages | to_entries[] | select(.value.hasInstallScript) | "\(.key) \(.value.resolved // "registry")"' package-lock.json
  1. On a local .tgz or directory dependency, set a version floor before you debug your own typing. PR #9914, "match allowScripts keys for local paths" by martinrrm, is the commit that made tarball and directory sources matchable at all, and it shipped in npm 12.1.0; the v11 line got the same change as #9929 in 11.20.0. Below those versions no spelling works on Windows, because the stored path carries backslashes while the key was parsed on forward slashes. npm -v decides whether you have a bug or a typo.
  2. Resolve relative keys from the project root, and confirm which root npm picked. #9914 also changed relative file: keys to resolve against the Arborist project root instead of the process cwd, so a key written from a subdirectory can need rewriting after the upgrade (and the reverse on anything older than 12.1.0). In a monorepo the answer is rarely the directory you are standing in:
npm prefix
  1. Grep your keys for store paths if install-strategy=linked was ever set. npm/cli #9939 records keys shaped like "file:../../../vue-demi@0.14.10-pGPQNZuy2Yfs8dw1nOAidw/node_modules/vue-demi": true, alongside a duplicate bare esbuild entry; the hash in that path changes on the next install, so the key can never match twice. Fixed by #9941 in 12.2.1. Find every hit and rewrite it as name@version:
jq -r '.allowScripts | keys[]' package.json | grep node_modules
  1. When the output is EALLOWSCRIPTS rather than a warning, stop editing package.json. npm/cli #9912 reported npm error --allow-scripts is not allowed in project-scoped installs. from a script as ordinary as "inst": "cd inner && npm install", because a persistent allow-scripts value in a user-level .npmrc was exported into the lifecycle environment and read by the inner npm. The discriminator is one install run two ways: cd inner && npm install succeeds while npm run inst fails, which is the shape described in --allow-scripts is not allowed in project-scoped installs. Fixed as #9913 in 12.1.0.
  2. Do not expect --ignore-scripts to rescue that case. The #9912 reporter states the failure persists with --ignore-scripts set, because the rejection happens while the inner npm parses config, before any decision about running a script exists. It is the first workaround most threads reach for, and it costs you an install cycle to learn nothing.
  3. Probe the npx path on its own; it is the one still open. npm/cli #9968 shows npm exec and npx exporting resolved config to child processes as npm_config_allow_scripts, so a tool launched through npx fails a project-scoped install with the same EALLOWSCRIPTS text. Reproduced on 12.0.2 and open as of this writing, which means a version bump is not the fix. One command prints whether your shell is carrying it:
npx -y -c 'node -e "console.log(process.env.npm_config_allow_scripts)"'

Clear the source with npm config delete allow-scripts --location=user, then run the probe again and expect undefined.

  1. Hunt for an inherited false before you add another true. Deny entries win over allow entries, and PR #9490, "recognize allowScripts for local link targets", was validated against exactly that shape: allowScripts: { "file:../testdep": false } on a local file: dependency. In a workspace the denial can sit in a root manifest nobody on your team edited this quarter:
jq -r '.allowScripts | to_entries[] | select(.value == false) | .key' package.json
  1. Run the pending list from the repo root, and treat the workspace answer as unverified. The npm-approve-scripts page documents --allow-scripts-pending as listing packages not yet covered without modifying package.json, and the same page states plainly that "This command is unaware of workspaces", so an approval taken from inside a workspace directory is a step to prove rather than trust. There is also a naming gap worth settling locally: the 12.0.0 changelog entry says npm install-scripts approve, the command page documents npm approve-scripts, and #9939's reproduction uses the former. npm help install-scripts prints which one your install actually has, and that output beats both docs pages. The scope difference between the two commands is laid out in npm approve-scripts vs --allow-scripts.

Version floors, so you can tell a bug from a typo before you spend an afternoon on key spelling:

SymptomIssue / PRFixed in npm 12v11 line
file: or local .tgz key never matches#9900 / #991412.1.011.20.0 (#9929)
user .npmrc breaks a nested install#9912 / #991312.1.0backport #9937
linked strategy writes store-path keys#9939 / #994112.2.1backport #10059
npx exports npm_config_allow_scripts#9968openopen

Wrap-up

The habit that catches all four: after every approval, read the file the script was supposed to write, and keep the pending list empty in CI rather than inspecting it after a failure. An empty array means every install script in the tree is covered; anything else names the packages still waiting on a decision.

npm approve-scripts --allow-scripts-pending --json