Is there an existing issue for this?
This issue exists in the latest npm version
This is not just a request to bump a dependency for a CVE
Current Behavior
Running these commands in the provided repo gives:
npm ci --workspace=pkg-b --include-workspace-root installs sass-embedded, even though:
pkg-b - is the only workspace in scope and declares no dependencies at all
sass-embedded is declared only by pkg-a, which is not in scope
vite (in scope, from the root) declares sass-embedded as an optional peer: peerDependenciesMeta: { "sass-embedded": { "optional": true } }
npm explain then describes the package npm has just installed as extraneous:
$ npm explain sass-embedded
sass-embedded@1.100.0 extraneous
node_modules/sass-embedded
peerOptional sass-embedded@"^1.70.0" from vite@8.2.1
node_modules/vite
So the resolver installs it while the explainer reports that nothing requires it.
The optional peer edge alone is not sufficient to cause this. vite also declares less, stylus and terser as optional peers, and none of those are installed, because no workspace declares them. It is the combination of "declared by some workspace anywhere in the monorepo" plus "an in-scope package has an optional peer edge to it" that makes a package survive scoping.
Prior issues I checked
I could not find an existing issue covering this case. The closest are all different:
Happy to close as a duplicate if I've missed one.
Why this matters
In a monorepo where each CI pipeline builds a single app, a scoped install is expected to fetch only that app's dependency closure. Packages pulled in this way tend to be large native toolchains. sass-embedded here is roughly 40 MB plus a platform-specific binary, so every pipeline pays to download and cache a compiler its app never invokes. There is no flag that removes it without also removing genuinely needed optional dependencies such as esbuild, rollup, or lint binaries.
Expected Behavior
--workspace=pkg-b should not install a package that is reachable only from an out-of-scope workspace. Either it should be pruned like any other out-of-scope dependency (which I would prefer), or — if installing it is intentional — npm explain should not then report it as extraneous.
Steps To Reproduce
Repro repo: https://github.com/davidsundelius/npm-workspace-optional-peer-repro
Three files:
package.json
{
"name": "npm-workspace-optional-peer-repro",
"version": "1.0.0",
"private": true,
"workspaces": ["pkg-a", "pkg-b"],
"devDependencies": { "vite": "^8.0.0" }
}
pkg-a/package.json
{ "name": "pkg-a", "version": "1.0.0", "devDependencies": { "sass-embedded": "^1.97.3" } }
pkg-b/package.json
{ "name": "pkg-b", "version": "1.0.0" }
Then:
npm install --package-lock-only
npm ci --workspace=pkg-b --include-workspace-root
ls node_modules/sass-embedded → present
npm explain sass-embedded → extraneous
Control, which isolates the cause
Change pkg-a to declare lodash (no peer edge from any installed package) instead of sass-embedded, and repeat. lodash is correctly pruned.
This rules out the simpler explanation that devDependencies of out-of-scope workspaces are never pruned — they are. Only the ones with an optional peer edge from an in-scope package survive.
Environment
Reproduced on every version tested, so this does not appear to be a recent regression:
| npm |
result |
| 10.9.2 |
sass-embedded installed |
| 11.4.2 |
sass-embedded installed |
| 11.6.0 |
sass-embedded installed |
| 11.12.1 |
sass-embedded installed |
| 12.0.2 (latest) |
sass-embedded installed |
- npm: 12.0.2
- Node.js: v25.9.0
- OS Name: macOS 26.6
- System Model Name: Apple Silicon (arm64)
Note: npm 12.0.2 warns that it does not support Node v25.9.0. The behaviour is identical on the earlier npm releases listed above, which do support the Node version used.
Is there an existing issue for this?
This issue exists in the latest npm version
This is not just a request to bump a dependency for a CVE
Current Behavior
Running these commands in the provided repo gives:
npm ci --workspace=pkg-b --include-workspace-rootinstallssass-embedded, even though:pkg-b- is the only workspace in scope and declares no dependencies at allsass-embeddedis declared only bypkg-a, which is not in scopevite(in scope, from the root) declaressass-embeddedas an optional peer:peerDependenciesMeta: { "sass-embedded": { "optional": true } }npm explainthen describes the package npm has just installed asextraneous:So the resolver installs it while the explainer reports that nothing requires it.
The optional peer edge alone is not sufficient to cause this.
vitealso declaresless,stylusandterseras optional peers, and none of those are installed, because no workspace declares them. It is the combination of "declared by some workspace anywhere in the monorepo" plus "an in-scope package has an optional peer edge to it" that makes a package survive scoping.Prior issues I checked
I could not find an existing issue covering this case. The closest are all different:
--workspace, and no duplication here)--omit=devstill installs devDependencies if they are workspace packages #6670 ---omit=devfailing for devDependencies that are themselves workspace packages (not peers; the package here is external)Happy to close as a duplicate if I've missed one.
Why this matters
In a monorepo where each CI pipeline builds a single app, a scoped install is expected to fetch only that app's dependency closure. Packages pulled in this way tend to be large native toolchains.
sass-embeddedhere is roughly 40 MB plus a platform-specific binary, so every pipeline pays to download and cache a compiler its app never invokes. There is no flag that removes it without also removing genuinely needed optional dependencies such asesbuild,rollup, or lint binaries.Expected Behavior
--workspace=pkg-bshould not install a package that is reachable only from an out-of-scope workspace. Either it should be pruned like any other out-of-scope dependency (which I would prefer), or — if installing it is intentional —npm explainshould not then report it asextraneous.Steps To Reproduce
Repro repo: https://github.com/davidsundelius/npm-workspace-optional-peer-repro
Three files:
package.json{ "name": "npm-workspace-optional-peer-repro", "version": "1.0.0", "private": true, "workspaces": ["pkg-a", "pkg-b"], "devDependencies": { "vite": "^8.0.0" } }pkg-a/package.json{ "name": "pkg-a", "version": "1.0.0", "devDependencies": { "sass-embedded": "^1.97.3" } }pkg-b/package.json{ "name": "pkg-b", "version": "1.0.0" }Then:
npm install --package-lock-onlynpm ci --workspace=pkg-b --include-workspace-rootls node_modules/sass-embedded→ presentnpm explain sass-embedded→extraneousControl, which isolates the cause
Change
pkg-ato declarelodash(no peer edge from any installed package) instead ofsass-embedded, and repeat.lodashis correctly pruned.This rules out the simpler explanation that devDependencies of out-of-scope workspaces are never pruned — they are. Only the ones with an optional peer edge from an in-scope package survive.
Environment
Reproduced on every version tested, so this does not appear to be a recent regression:
sass-embeddedinstalledsass-embeddedinstalledsass-embeddedinstalledsass-embeddedinstalledsass-embeddedinstalledNote: npm 12.0.2 warns that it does not support Node v25.9.0. The behaviour is identical on the earlier npm releases listed above, which do support the Node version used.