Skip to content

fix(arborist): repair wrong-but-existing symlink target in linked strategy - #9644

Merged
owlstronaut merged 1 commit into
release/v11from
backport/v11/9628
Jun 24, 2026
Merged

fix(arborist): repair wrong-but-existing symlink target in linked strategy#9644
owlstronaut merged 1 commit into
release/v11from
backport/v11/9628

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Backport of #9628 to release/v11.

…ategy (#9628)

In continuation of our exploration of using `install-strategy=linked` in
the [Gutenberg
monorepo](WordPress/gutenberg#75814), which
powers the WordPress Block Editor.

Under `install-strategy=linked`, if a top-level `node_modules/<dep>`
symlink points to a store key that exists on disk but is the **wrong
version**, re-running `npm install` does not repair it. npm reports
success and leaves the dependency resolving to the wrong version. This
is the state an interrupted update leaves behind: the new store key is
extracted but the symlink has not yet been repointed.

A symlink pointing at a **non-existent** target is already repaired on
reinstall; only a wrong-but-existing target slips through, because
cleanup validates the link name, not its target.

## Why

For linked installs, `#buildLinkedActualForDiff` synthesizes the
"actual" tree the diff compares against from the **ideal** children,
never reading the real on-disk symlink target. So a link whose on-disk
target is a valid-but-wrong store key looks identical to the ideal node,
the diff reports no change, and the symlink is left untouched. The
hoisted strategy is unaffected because it self-heals the analogous
corruption.

## How

In `#buildLinkedActualForDiff`, when an existing link's resolved on-disk
target differs from its ideal target, skip creating a synthetic actual
entry for it. With no actual entry to match, the diff treats the link as
an `ADD`, and `#reifyNode` removes the old symlink and recreates it
pointing at the correct store key. A new `#linkTargetMismatch` helper
compares the two resolved targets; it runs only after the existing
`existsSync` guards, so both paths are known to exist.

This repairs both the top-level symlink and wrong transitive/sibling
links inside the store, and leaves already-correct trees untouched (no
spurious relinking on an idempotent reinstall).

## References

Fixes #9611

(cherry picked from commit 0ffce98)
@owlstronaut
owlstronaut merged commit 4e40b1c into release/v11 Jun 24, 2026
33 checks passed
@owlstronaut
owlstronaut deleted the backport/v11/9628 branch June 24, 2026 18:38
@github-actions github-actions Bot mentioned this pull request Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants