Summary
Setting allow-scripts in a user or local (non-project) .npmrc makes a project's own package.json script fail with:
npm error code EALLOWSCRIPTS
npm error --allow-scripts is not allowed in project-scoped installs.
Add the entries to the "allowScripts" field in package.json, or to .npmrc, instead.
This happens even though the user never passed --allow-scripts β the value lives in a persistent .npmrc exactly as npm config set allow-scripts=<pkg> --location=user writes it β and even when the script explicitly passes --ignore-scripts.
The project package.json in question contains scripts like:
"scripts": {
"inst": "npm ci --ignore-scripts",
"perf": "node tests/ensure-playwright.mjs"
}
- Running
npm ci --ignore-scriptsdirectly in the workspace: β succeeds (no error). - Running
npm run inst(which runs that exact same command): β fails withEALLOWSCRIPTS. - Running
node tests/ensure-playwright.mjsdirectly: β succeeds. - Running
npm run perf(which runs that exact samenode β¦command, and that script internally spawns annpm install): β fails withEALLOWSCRIPTS.
The only difference between the working and failing invocations is whether the npm install/npm ci process is reached through npm run-script. npm run exports the resolved npm config as npm_config_* environment variables for the lifecycle script's child process. Because the outer process read allow-scripts from a user/local .npmrc, the inner npm install/npm ci inherits npm_config_allow_scripts and reads it via its env source. resolveAllowScripts groups the cli and env sources together and rejects any policy from them in a project-scoped install, so the inner install aborts β even though the value originated in a .npmrc, not a command-line flag, and even though the inner install was told --ignore-scripts.
This is the same root cause as npm/cli#9783 (git-dep preparation), just reached through a different "shell-out to an inner npm install" path: an ambient npm_config_allow_scripts from a persistent .npmrc is misclassified as a forbidden CLI/env policy.
Environment
- npm:
12.0.2 - node:
v26.6.0 - OS: Windows 11
allow-scriptsentry in user.npmrc:["opencode-ai"]nodeexecutable:C:\nvm4w\nodejs\node.exenpm local prefix=C:\Users\xxx\xxx\xxxHOME=C:\Users\xxxcwd=C:\Users\xxx\xxx\xxx(a real project workspace, not global context)
How the offending setting gets there
This is the documented, recommended workflow. Installing a global package with an install script prints:
npm warn allow-scripts <pkg>@x.y.z (postinstall: node install.cjs)
npm warn allow-scripts Run `npm install -g --allow-scripts=<pkg>` to allow
these scripts once, or `npm config set allow-scripts=<pkg> --location=user`
to allow them for all global installs.
Following the second suggestion writes allow-scripts=<pkg> to the user .npmrc. From then on, npm run in any project on the machine that triggers an inner npm install fails β even ones that do not run any scripts and even ones that explicitly request --ignore-scripts.
Minimal, self-contained reproduction (no network / external repo required)
R=$(mktemp -d); cd "$R"
# A user/local .npmrc with the entry exactly as
# `npm config set allow-scripts=... --location=user` would write it.
echo "allow-scripts=whatever" > "$R/userrc"
mkdir app && cd app
npm init -y >/dev/null
# A script that spawns an inner `npm install` (mirrors `npm ci` / ensure-playwright.mjs).
mkdir inner && (cd inner && npm init -y >/dev/null)
cat > package.json <<'EOF'
{ "name": "app", "version": "1.0.0",
"scripts": { "inst": "cd inner && npm install" } }
EOF
# (A) Direct inner install β works
(cd inner && npm install --userconfig "$R/userrc"); echo "direct exit: $?"
# (B) Same inner install, but reached through `npm run-script` β fails
npm run inst --userconfig "$R/userrc"; echo "npm-run exit: $?"
Actual result (B)
npm error code EALLOWSCRIPTS
npm error --allow-scripts is not allowed in project-scoped installs.
Add the entries to the "allowScripts" field in package.json, or to .npmrc, instead.
Expected result
Both (A) and (B) complete. An allow-scripts value that lives in a persistent user/local .npmrc is, by definition, not a command-line flag. When a script is run inside a project (notably a script defined in that project's own package.json), npm should ignore non-project (user/local) .npmrc allow-scripts rather than forwarding it into the child npm install as a forbidden env-layer policy β and it must never abort a command that was explicitly told --ignore-scripts.
Notes on the trigger
- The failure requires the inner process to be an
npm install/npm cireached throughnpm run-script(or any lifecycle script that shells out to npm). The reproduction above uses a trivial inner install with no scripts at all, proving the policy check itself β not any real script execution β is what trips. - It is independent of where the setting lives: reproduced identically with the entry in the user
.npmrcand the global (etc/npmrc) file. There is no persistent-config location that both silences the global-install script warning and leavesnpm runworking. - It reproduces even when the outer/script command passes
--ignore-scripts, because theEALLOWSCRIPTSthrow atresolveAllowScriptshappens purely from the presence of theenv-layer policy, before--ignore-scriptsis ever consulted.
Root cause (source pointers)
lib/utils/resolve-allow-scripts.js:107throwsEALLOWSCRIPTSwhen a policy is found in thecli/envsources and the install is not global and notskipProjectConfig. The grouping:
const cliPolicy = policyFromSources(npm, ['cli', 'env'])
...
if (cliPolicy && !npm.global && !skipProjectConfig) {
throw Object.assign(new Error(
'--allow-scripts is not allowed in project-scoped installs. ' +
'Add the entries to the "allowScripts" field in package.json, ' +
'or to .npmrc, instead.'), { code: 'EALLOWSCRIPTS' })
}
npm run-scriptexports the resolved config into the child process's environment asnpm_config_*variables (so lifecycle scripts can read e.g.process.env.npm_config_registry). Aallow-scriptsvalue sourced from a user/local.npmrctherefore arrives in the innernpm installas anenv-layer value, whichpolicyFromSources(npm, ['cli', 'env'])reads andresolveAllowScriptstreats as a forbidden CLI flag.
This is the same defect described in npm/cli#9783, where the ambient npm_config_allow_scripts instead arrives via the git-dep preparation inner install. The fix space is identical; npm run-script is simply an additional path that injects the env-layer value.
Suggested fixes
- (Preferred) When
npm run-script(and the git-dep preparation path from #9783) spawns an innernpm install, do not forwardnpm_config_allow_scriptsfrom the ambient environment into the child. The inner install already receives a deliberately curated flag set, so script policy for the inner build should be passed explicitly (or omitted) rather than inherited ambiently. This leaves thecli/envgrouping inresolveAllowScriptsintact per RFC 868, while fixing the ambient-env inheritance that misclassifies a.npmrcvalue as a CLI policy. - Alternatively, in
npm run-script, stripallow-scriptsfrom the exportednpm_config_*env when the script is project-scoped, so a user/local.npmrcpolicy is not re-applied to the project's own inner install. - At minimum, fix the error message: when the value came from a persistent
.npmrc(directly or vianpm_config_*env inheritance), do not tell the user to "add the entries to.npmrc" β that is where it already is, and the advice sends them in a circle. The message should also not fire when the offending command was invoked with--ignore-scripts.
Related
- npm/cli#9783 β same
EALLOWSCRIPTSmisclassification, reached via the git-dependency preparation inner install. This issue is thenpm run-scriptsibling of that bug.
Workaround (for anyone hitting this)
Do not persist allow-scripts in a user/global .npmrc. Instead pass it only at the moment of the global install that needs it:
npm install -g <pkg> --allow-scripts=<pkg>
This approves the global install's script without leaving a persistent setting that later breaks npm run in unrelated projects. A project-scoped workaround is to move the policy into the project's own package.json allowScripts field (or project .npmrc) β but note that, per the linked #9783, the project-own-.npmrc path can still be hit via the env inheritance, so the robust fix is to avoid the ambient user/local entry entirely.