Skip to content

fix(arborist): clean up orphaned scoped store entries in linked strategy#9441

Open
manzoorwanijk wants to merge 1 commit into
npm:latestfrom
manzoorwanijk:fix/linked-orphan-scoped-store-entries
Open

fix(arborist): clean up orphaned scoped store entries in linked strategy#9441
manzoorwanijk wants to merge 1 commit into
npm:latestfrom
manzoorwanijk:fix/linked-orphan-scoped-store-entries

Conversation

@manzoorwanijk
Copy link
Copy Markdown
Contributor

In continuation of our exploration of using install-strategy=linked in the Gutenberg monorepo, which powers the WordPress Block Editor.

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.
The store key itself spans two path segments.

When a scoped dependency is updated or removed, the orphan-cleanup pass in #cleanOrphanedStoreEntries failed to remove the stale store entry, so the store accumulated dead scoped entries on every update.
This never happened for unscoped packages.

The root cause is that the cleanup made a single-segment assumption in two places.
When collecting valid keys from the ideal tree, loc.split('/')[2] returned just @scope instead of the full @scope/pkg@version-hash key.
When enumerating on-disk entries, the non-recursive readdir(storeDir) returned the @scope directory as a single entry and never descended into the per-version subdirectories inside it.
The orphan filter then compared @scope on disk against @scope in the valid set, so the scope directory was always considered valid and never swept, no matter how many stale versions it contained.

Fixed by reconstructing the full two-segment key for scoped entries when collecting valid keys, and by descending one level into each @scope directory when enumerating on-disk entries so the real per-version entries are listed and compared as full keys.
Both sides are compared in forward-slash form, matching how ideal-tree locations are already normalized, so resolve reconstructs the on-disk path correctly on POSIX and Windows.

Removing the last scoped orphan under a scope would leave an empty @scope directory behind, so after removing orphans the pass now prunes any scope directory that has become empty.

References

Fixes #9440

@manzoorwanijk manzoorwanijk marked this pull request as ready for review May 30, 2026 09:42
@manzoorwanijk manzoorwanijk requested review from a team as code owners May 30, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant