---
title: "npm 11.18 Promotes the `linked` Install Strategy to Stable, Adds the `npm install-scripts` Namespace, and Warns When `min-release-age` Blocks an Audit Fix"
description: "npm 11.18.0 (June 29, 2026) ships three features and a long backlog of bug fixes that together finish the work the npm CLI has been doing on the `install-strategy=linked` (isolated) install mode since RFC #0042 in 2022. The headline is [PR #9677](https://github.com/npm/cli/pull/9677) (backport of #9674), which graduates `--install-strategy=linked` from experimental to stable. The mode installs every package into `node_modules/.store/<name>@<version>/node_modules/<dep>` and links each into its parent's `node_modules` tree, so a package can only `require` dependencies that are actually declared in its own `package.json`. The new docs recommendation ([PR #9690](https://github.com/npm/cli/pull/9690)) is to run `--install-strategy=linked` in CI to catch phantom dependencies before publishing. Around the graduation the release ships a namespaced `npm install-scripts` command ([#9635](https://github.com/npm/cli/pull/9635), backport of #9629) that owns `approve`, `deny`, and `ls`, with `npm approve-scripts` / `npm deny-scripts` kept as aliases; an `install-scripts: prune unused allowScripts entries` housekeeping pass ([#9662](https://github.com/npm/cli/pull/9662)); and a new warning when `min-release-age` blocks an `npm audit fix` ([#9564](https://github.com/npm/cli/pull/9564)). The 43-commit release also fixes 19 `linked` strategy bugs (audit determinism #9638, dangling `.bin` shims #9643, stale `.store` cleanup #9649, invalid `filterNode` crash #9645, peerOptional validation #9641), three `npm sbom` fixes, and a percent-encoded `vcs_url` purl fix ([#9693](https://github.com/npm/cli/pull/9693))."
date: 2026-06-30
image: "/images/heroes/2026-06-30--npm-11-18-linked-install-strategy-stable-phantom-deps.png"
author: lschvn
tags: ["tooling", "ecosystem"]
tldr:
  - "[npm 11.18.0](https://github.com/npm/cli/releases/tag/v11.18.0) (June 29, 2026) graduates `--install-strategy=linked` from experimental to stable via [PR #9677](https://github.com/npm/cli/pull/9677), the backport of [PR #9674](https://github.com/npm/cli/pull/9674). The mode was originally added in [PR #6078](https://github.com/npm/cli/pull/6078) on January 25, 2023 against RFC [npm/rfcs#0042](https://github.com/npm/rfcs/blob/main/accepted/0042-isolated-mode.md) and has been tagged experimental ever since. The promotion removes the implicit 'this might eat your node_modules' warning the docs have carried for 3.5 years and is the closing event of the isolated-mode work the npm team started at the 2022 Contributor Summit."
  - "The release ships the new `npm install-scripts` command namespace ([#9635](https://github.com/npm/cli/pull/9635), backport of [#9629](https://github.com/npm/cli/pull/9629)) that owns `approve`, `deny`, and `ls`. The old `npm approve-scripts` and `npm deny-scripts` commands are kept as aliases for one release. The release also adds `install-scripts: prune unused allowScripts entries` ([#9662](https://github.com/npm/cli/pull/9662)), `npm sbom` percent-encodes the `vcs_url` qualifier in generated purls ([#9693](https://github.com/npm/cli/pull/9693)), and `npm audit fix` now warns explicitly when `minimumReleaseAge` blocks the fix it would otherwise apply ([#9564](https://github.com/npm/cli/pull/9564))."
  - "Around the graduation, 19 of the 43 commits are bug fixes for the `linked` strategy itself: audit determinism under linked (#9638), audit of the non-isolated tree under the linked strategy (#9631), dangling `.bin` shims after uninstall (#9643), stale `.store` and hoisted dirs cleanup on strategy switch (#9649), invalid `filterNode` crash (#9645), wrong-but-existing symlink target repair (#9644), peerOptional validation in no-save mutations (#9641), `npm exec` resolving workspace-local bins under linked (#9648), and `npm query` reporting logical dep location under linked (#9664). Together they make `linked` a viable alternative to the default `hoisted` strategy for daily development, not just for CI smoke-tests."
faq:
  - question: "What does the `linked` install strategy actually change?"
    answer: "The default `hoisted` strategy duplicates transitive dependencies into the top-level `node_modules` so any package can `import` any other transitive package that happens to be hoisted, even when nothing in `package.json` declares it. That is the root cause of phantom dependencies: code that imports a library it never added to its own `dependencies`, and the bug only surfaces when a coworker installs with a clean `node_modules` and the hoisting happens to be different. `--install-strategy=linked` (also called isolated mode) instead installs every package into `node_modules/.store/<name>@<version>/node_modules/<dep>` and symlinks each package's declared dependencies into its own `node_modules/<dep>`. The result is that a package can only `import` what it declared. If it imports an undeclared library, the import fails immediately instead of working by accident and shipping broken."
  - question: "Why is this a big deal if the strategy has been around since 2023?"
    answer: "Three reasons. First, the strategy was tagged experimental for 3.5 years, which gave most teams a reason not to depend on it for production: a stable strategy is a strategy you can put in `~/.npmrc` and forget about. Second, the experimental flag came with sharp edges: audit was unreliable under linked (#9609), `npm ls` reported false `UNMET DEPENDENCY` entries (#9095), `npm install --audit` returned zero results on a real vulnerability (#9609), and `napi-postinstall` could not resolve an installed optional native binding (#9620). The 19 bug fixes in 11.18 close those gaps. Third, the npm docs now actively recommend `linked` for package authors in CI ([PR #9690](https://github.com/npm/cli/pull/9690)): the docs page reads 'We recommend that package authors use --install-strategy=linked during development to catch undeclared (`phantom`) dependencies before publishing.'"
  - question: "What is `npm install-scripts` and what changes for me?"
    answer: "`npm install-scripts` is a new top-level command namespace introduced by [PR #9635](https://github.com/npm/cli/pull/9635) (backport of [#9629](https://github.com/npm/cli/pull/9629)). It owns three subcommands: `npm install-scripts approve`, `npm install-scripts deny`, and `npm install-scripts ls`. The previous `npm approve-scripts` and `npm deny-scripts` commands are kept as aliases for one release, then retired. The namespace also re-points the install-time, rebuild, and strict-allow-scripts guidance at the new commands. The reason for the namespace is that 'approve-scripts' was a special-case command sitting next to the rest of npm's command surface; `install-scripts` makes it part of the same family as `install`, `install-test`, and the rest. The release also adds `install-scripts: prune unused allowScripts entries` ([#9662](https://github.com/npm/cli/pull/9662)) so the entry list shrinks when packages are removed from `dependencies`."
  - question: "What does the new `min-release-age` audit-fix warning do?"
    answer: "[PR #9564](https://github.com/npm/cli/pull/9564) (backport of [#9544](https://github.com/npm/cli/pull/9544)) teaches `npm audit fix` to detect when the fix it would apply is blocked by the `minimumReleaseAge` policy set in `.npmrc` (the same policy that [Astro 6.4.4](/articles/2026-06-05--astro-6-4-4-routing-i18n-dev-fixes) surfaces in its own update flow). When the fix is blocked by the policy, npm 11.18 prints an explicit warning telling the user which package would be skipped and which policy flag is blocking it, instead of silently doing nothing. The same policy shipped in [pnpm 11.8](/articles/2026-06-19--pnpm-11-8-dry-run-install-node-package-map-sbom) and is now part of the baseline for any package manager that wants to coordinate with monorepo owners who gate new releases for a cool-down window."
  - question: "How does this compare to pnpm and yarn?"
    answer: "pnpm has used an isolated layout by default since the project's first release in 2017 and has never carried the experimental flag: every package can only `require` what it declares, and phantom dependencies are impossible by construction. Yarn berry (Yarn 2+) ships a similar `nodeLinker: pnp` mode and an `nmHoistingLimits` setting that approximates isolation. npm's `linked` mode is npm's equivalent of those, and 11.18 is the release where npm is willing to recommend it as the daily-driver install strategy for package authors, the same way pnpm does. The three package managers now agree on the install model, the difference is which one is the default: pnpm (isolated, never experimental), yarn berry (PnP by default, with hoisted as opt-in), npm hoisted (with linked as opt-in)."
  - question: "Is npm 11.18 safe to upgrade to today?"
    answer: "For the vast majority of projects yes: the default `hoisted` install strategy is unchanged, the CLI surface changes are additive (new `npm install-scripts` namespace, aliases kept for one release), and the SBOM, audit, and `npm ls` paths get bug fixes that improve correctness. The two reasons to wait are (1) your project depends on a package that uses an install hook in a way that the new strict-allow-scripts audit treats as inert and flags; the [June 6 npm supply chain piece](/articles/2026-06-06--npm-supply-chain-attack-red-hat-mini-shai-hulud) covers the relevant flags and (2) you use `npm link` heavily against a workspace that has its own `.store`, where the strategy-switch cleanup ([#9649](https://github.com/npm/cli/pull/9649)) is a behavior change rather than a bug fix. For everyone else, `npm install -g npm@11` and the new `linked` mode is a one-flag upgrade."
---

[npm 11.18.0](https://github.com/npm/cli/releases/tag/v11.18.0), published on June 29, 2026, is the release that finishes the work the npm CLI has been doing on isolated installs for the last three and a half years. The headline is [PR #9677](https://github.com/npm/cli/pull/9677) (backport of [#9674](https://github.com/npm/cli/pull/9674)), which graduates `--install-strategy=linked` from experimental to stable. The mode was added in [PR #6078](https://github.com/npm/cli/pull/6078) on January 25, 2023 against [npm/rfcs#0042](https://github.com/npm/rfcs/blob/main/accepted/0042-isolated-mode.md), the isolated-mode RFC that came out of the 2022 Contributor Summit. The promotion removes the implicit "this might eat your node_modules" warning the docs have carried since 2023 and is the closing event of the npm team's longest-running install-strategy effort.

The release is the second feature release of the v11 line this month and ships alongside the [npm v12 prerelease](https://github.com/npm/cli/releases/tag/v12.0.0-pre.2) the team tagged the same day. The v11 line is the stable line; the v12 prerelease is where the [block-unreviewed-install-scripts-by-default change](https://github.com/npm/cli/pull/9424) and the [linked mode graduation](https://github.com/npm/cli/pull/9674) get re-applied against the upcoming major.

## What `--install-strategy=linked` actually does

The default npm install strategy is `hoisted`. Every transitive dependency is duplicated into the top-level `node_modules` so any package can `require` any other transitive package that happens to be hoisted, even when nothing in `package.json` declares it. That is the root cause of phantom dependencies: code that imports a library it never added to its own `dependencies`, and the bug only surfaces when a coworker installs with a clean `node_modules` and the hoisting happens to be different.

`--install-strategy=linked` (also called isolated mode) installs every package into `node_modules/.store/<name>@<version>/node_modules/<dep>` and symlinks each package's declared dependencies into its own `node_modules/<dep>`. The result is that a package can only `import` what it declared. If it imports an undeclared library, the import fails immediately instead of working by accident and shipping broken.

The npm [install docs](https://docs.npmjs.com/cli/v11/commands/npm-install#install-strategy) describe the four strategies this way:

- `hoisted` (default): install non-duplicated at top level, duplicated as necessary within directory structure.
- `nested` (formerly `--legacy-bundling`): install in place, no hoisting.
- `shallow` (formerly `--global-style`): only install direct deps at top level.
- `linked`: install in `node_modules/.store`, link in place, unhoisted.

The release notes for 11.18 add the docs recommendation that made the strategy a real recommendation rather than a hidden CLI flag. The docs page now reads: "We recommend that package authors use `--install-strategy=linked` during development to catch undeclared (`phantom`) dependencies before publishing: the isolated layout only exposes a package's declared dependencies, so an `import` of a package that was never added to `package.json` can fail instead of resolving by accident and shipping broken. See [Catching undeclared (`phantom`) dependencies](/cli/v11/using-npm/developers#catching-undeclared-phantom-dependencies)."

That sentence was added by [PR #9690](https://github.com/npm/cli/pull/9690), the docs change that backports alongside the graduation.

## Why the 3.5-year experimental period

Three reasons. First, the experimental flag came with sharp edges. The team's [tracking issue #9608](https://github.com/npm/cli/issues/9608) (`[Tracking] install-strategy=linked (isolated mode) bugs`) lists 19 separate bugs across the install path, the audit path, the `npm exec` path, and the `npm ls` path that the 11.18 release closes. The four that the team called out as the worst are [audit returning zero results on a real vulnerability](https://github.com/npm/cli/issues/9609) (#9609), [`npm ls` reporting false `UNMET DEPENDENCY`](https://github.com/npm/cli/issues/9095) (#9095), [`napi-postinstall` failing to resolve an installed optional native binding](https://github.com/npm/cli/issues/9620) (#9620), and the [.store layout mismatch with the hidden lockfile](https://github.com/npm/cli/issues/9612) (#9612). The 11.18 fixes for each of those are PRs #9638, #9095's follow-on under #9638's logic, #9620's fix under #9665, and #9642 respectively.

Second, the strategy needed an audit story. [PR #9625](https://github.com/npm/cli/pull/9625) audits the non-isolated tree under the linked strategy, so `npm audit` walks both the linked view and the underlying store and reports vulnerabilities accurately. [PR #9638](https://github.com/npm/cli/pull/9638) makes the audit report deterministic by re-attaching the dropped `via` links so two consecutive `npm audit` runs on an unchanged lockfile produce byte-identical output, which the team's CI infrastructure depends on for diff-based regression tests.

Third, the workspace story. [PR #9666](https://github.com/npm/cli/pull/9666) and [PR #9665](https://github.com/npm/cli/pull/9665) teach the linked strategy to load transitive optional deps and to apply `dev`/`prod` flags correctly inside workspaces; [#9648](https://github.com/npm/cli/pull/9648) makes `npm exec` resolve workspace-local bins; [#9669](https://github.com/npm/cli/pull/9669) surfaces undeclared workspaces under the linked strategy so a missing `workspaces` entry does not silently produce a partial install. Before 11.18 the linked strategy worked for single-package repos but tripped over workspace setups; after 11.18 it is the recommended daily driver for workspaces.

## The new `npm install-scripts` namespace

[PR #9635](https://github.com/npm/cli/pull/9635) (backport of [#9629](https://github.com/npm/cli/pull/9629)) introduces a namespaced `npm install-scripts` command that owns three subcommands: `npm install-scripts approve`, `npm install-scripts deny`, and `npm install-scripts ls`. The previous `npm approve-scripts` and `npm deny-scripts` commands are kept as aliases for one release, then retired. The namespace re-points the install-time, rebuild, and `strict-allow-scripts` guidance at the new commands so a developer who runs `npm install` and hits the new `npm install blocked N scripts` line gets pointed at the same family of commands.

The reason for the namespace is that `approve-scripts` was a special-case command sitting next to the rest of npm's command surface; `install-scripts` makes it part of the same family as `install`, `install-test`, and the rest. It also gives the team room to add subcommands: `npm install-scripts ls` is new in 11.18 and prints the current allow/deny list with package names and paths.

The release also adds [PR #9662](https://github.com/npm/cli/pull/9662) (`install-scripts: prune unused allowScripts entries`), which sweeps the allow list every install and removes entries whose package is no longer in `dependencies`. This closes a long-standing wart where the allow list grew monotonically: if you approved `esbuild` for `package-a`, then removed `esbuild` from `package-a`'s deps and added it to `package-b`'s deps, the allow list kept the original entry. After 11.18 the sweep removes it as part of `npm install`, and the user is asked to re-approve under `package-b`.

## The `min-release-age` audit-fix warning

[PR #9564](https://github.com/npm/cli/pull/9564) (backport of [#9544](https://github.com/npm/cli/pull/9544)) teaches `npm audit fix` to detect when the fix it would apply is blocked by the `minimumReleaseAge` policy set in `.npmrc`. The same policy is shipped by [pnpm](https://pnpm.io/npmrc#minimum-release-age), and the [Astro 6.4.4](/articles/2026-06-05--astro-6-4-4-routing-i18n-dev-fixes) update flow surfaces it for `@astrojs/upgrade`. The npm version warns explicitly when the policy blocks an audit fix, instead of silently doing nothing:

```
npm audit fix
# 3 vulnerabilities (1 low, 1 moderate, 1 high)
# 
# Could not auto-fix 1 vulnerability:
#   package: glob-parent <6.0.0 (transitive via some-tool)
#   blocked by minimumReleaseAge=4320 (cool-down window)
#   set `minimum-release-age=0` or wait for the cool-down to expire.
```

The text is the same shape as the pnpm `ERR_PNPM_MIN_RELEASE_AGE` warning. The npm team's PR description says they "want users to be able to tell the difference between 'no fix available' and 'fix exists but is being held back by policy'," which the previous `npm audit fix` output did not.

## SBOM and other fixes

Three `npm sbom` fixes ship in 11.18. [PR #9693](https://github.com/npm/cli/pull/9693) percent-encodes the `vcs_url` qualifier in the generated purls, which closes a long-standing CP-tooling bug where a `git+https://github.com/foo bar/bar` URL (with a space) was emitted verbatim and rejected by downstream parsers. [#9631](https://github.com/npm/cli/pull/9631) audits the non-isolated tree under the linked strategy so the SBOM and `npm audit` reports both pick up the full dep set. [#9641](https://github.com/npm/cli/pull/9641) validates `peerOptional` conflicts in no-save mutations, which means `npm install some-pkg --no-save` will refuse to install if it would create a peerOptional conflict.

The release also fixes [PR #9602](https://github.com/npm/cli/pull/9602) (don't flag inert optional deps in `strict-allow-scripts`), [#9607](https://github.com/npm/cli/pull/9607) (approve deps with no resolved URL by name), and [#9663](https://github.com/npm/cli/pull/9663) (close allowScripts enforcement gaps). Together they mean the `strict-allow-scripts` mode added in [npm 11.5](/articles/2026-06-06--npm-supply-chain-attack-red-hat-mini-shai-hulud) now correctly distinguishes between packages that need scripts (the install hook is on, the audit hook will catch them) and packages that don't (the install hook is off because there is no install script).

## Why this matters

`--install-strategy=linked` is npm's answer to the question pnpm has been answering since 2017: how do you stop a package from importing a transitive dependency it never declared? pnpm never carried the experimental flag; yarn berry ships PnP by default; npm is the third of the three to reach "stable and recommended for daily use." The graduation is the moment when the install model that everyone else treats as the default is finally a default-recommended path on npm.

For package authors the practical change is small and concrete. Add this to `.npmrc`:

```
install-strategy=linked
```

Then run `npm install` in CI as part of `npm publish`. If the build now fails because some test file imports an undeclared library, you have a real bug to fix before publishing, not a phantom dep that will explode in a coworker's clean install.

The same change applies to monorepo owners. The 11.18 fixes for `npm exec` (#9648), `npm ls` (#9664), workspaces (#9666), and the `.store` cleanup on strategy switch (#9649) close the gaps that made `--install-strategy=linked` unusable for workspaces before. A monorepo can ship `install-strategy=linked` in `.npmrc` at the root and rely on it propagating through workspaces.

The release is also the first one since the [June 6 supply chain piece](/articles/2026-06-06--npm-supply-chain-attack-red-hat-mini-shai-hulud) that closes the strict-allow-scripts audit gaps from that article. The `--install-blocked-scripts` and `strict-allow-scripts` flags from npm 11.5 are now usable end-to-end, and the new `npm install-scripts` namespace is where the approvals live.