Skip to content

[BUG] --install-strategy=linked leaves orphaned .store/ entries when dependencies change #9032

@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

When using --install-strategy=linked, updating or removing a dependency leaves the old .store/ directory behind. Each install creates a new store entry keyed by name@version-<treeHash>, but the previous one is never cleaned up. Over time, node_modules/.store/ accumulates orphaned directories that waste disk space.

This is particularly wasteful because each store entry contains the package and all of its transitive dependencies as nested directories. A single orphaned entry for something like express can be a significant directory tree.

The problem occurs when:

  • A dependency is upgraded (e.g. chalk@4 -> chalk@5)
  • A dependency is removed from package.json
  • The dependency tree is restructured (same version but different treeHash)

Expected Behavior

Old .store/ entries that are no longer referenced by any symlink should be removed during install, similar to how the hoisted and shallow strategies remove packages that are no longer needed.

Steps To Reproduce

mkdir /tmp/test-orphan && cd /tmp/test-orphan
npm init -y

# First install
npm install chalk@4 --install-strategy=linked
ls node_modules/.store/
# Shows: chalk@4.1.2-<hash>/ and transitive dep entries

# Update to new version
npm install chalk@5 --install-strategy=linked
ls node_modules/.store/
# Shows both: chalk@4.1.2-<oldhash>/ (orphan) AND chalk@5.4.1-<newhash>/

# The old entry is never cleaned up, even with explicit prune
npm prune --install-strategy=linked
ls node_modules/.store/
# chalk@4.1.2-<oldhash>/ is still there

Additional Context

Related to #6100. The hoisted and shallow strategies don't have this problem because loadActual() can see all packages as direct children of root, so the diff correctly produces REMOVE actions for packages no longer needed. The linked strategy's flat .store/ layout is invisible to the diff's allChildren() traversal of the actual tree, so orphaned entries never get REMOVE actions.

Environment

  • npm: 11.11.0
  • Node.js: v22.20.0
  • OS Name: macOS

Metadata

Metadata

Assignees

No one assigned

    Labels

    Bugthing that needs fixingNeeds Triageneeds review for next steps

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions