Skip to content

[BUG] Orphaned scoped store entries are not cleaned up under install-strategy=linked #9440

@manzoorwanijk

Description

@manzoorwanijk

Is there an existing issue for this?

  • I have searched the existing issues

This issue exists in the latest npm version

  • I am using the latest npm

Current Behavior

Found while testing #9439.

Under --install-strategy=linked, each external package is extracted into a content-addressed store entry keyed by name@version-hash.
For scoped packages the key contains the unescaped scope slash, so the entry nests one extra directory level: node_modules/.store/@scope/pkg@version-hash/node_modules/@scope/pkg.

When a scoped dependency is updated (or otherwise re-resolved to a new hash), the orphan-cleanup pass that runs after a linked install fails to remove the stale store entry.
The old node_modules/.store/@scope/pkg@oldversion-hash/ directory is left behind indefinitely.
The store accumulates dead scoped entries on every update, which never happens for unscoped packages.

Note this is specifically the cleanup granularity bug.
If the last package under a scope is orphaned, an empty @scope directory is also left behind.

Expected Behavior

After a linked install, store entries that are no longer referenced by the ideal tree should be removed, including scoped ones.
Updating @scope/pkg from one version to another should leave exactly one store entry for that package, just like the unscoped case.
Empty @scope directories should not be left behind in node_modules/.store/.

Steps To Reproduce

mkdir scoped-store-repro && cd scoped-store-repro
npm init -y

npm install @isaacs/cliui@8.0.2 --install-strategy=linked
ls node_modules/.store/@isaacs/
# cliui@8.0.2-<hash>

npm install @isaacs/cliui@9.0.0 --install-strategy=linked
ls node_modules/.store/@isaacs/
# cliui@8.0.2-<hash>   ❌ stale entry still present
# cliui@9.0.0-<hash>

For contrast, an unscoped package is cleaned up correctly:

mkdir unscoped-store-repro && cd unscoped-store-repro
npm init -y

npm install abbrev@2.0.0 --install-strategy=linked
ls node_modules/.store/ | grep abbrev
# abbrev@2.0.0-<hash>

npm install abbrev@3.0.0 --install-strategy=linked
ls node_modules/.store/ | grep abbrev
# abbrev@3.0.0-<hash>   ✅ old abbrev@2.0.0 entry removed

The same result happens if instead of installing a new version directly, the version is bumped in package.json and npm install --install-strategy=linked is re-run.
A regression test mirroring the existing unscoped test in workspaces/arborist/test/isolated-mode.js ("orphaned store entries are cleaned up on dependency update"), but using a scoped dependency, fails on the assertion that the old store entry is removed.

Environment

  • npm: 11.16.0
  • Node.js: v24.15.0
  • OS Name: macOS (Darwin 25.5.0)
  • System Model Name: Mac
  • npm config:
install-strategy = "linked"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions