Skip to content

fix(arborist): preserve root peer sets during forced updates - #10164

Open
umtong wants to merge 1 commit into
npm:latestfrom
umtong:fix/preserve-shared-peer-entry-deps
Open

umtong wants to merge 1 commit into
npm:latestfrom
umtong:fix/preserve-shared-peer-entry-deps

Conversation

@umtong

@umtong umtong commented Oct 26, 2026 •

Copy link
Copy Markdown

Fixes #7349.

A forced update can loop indefinitely when a conflicting peer dependent is shared by the project and a transitive dependency. While pruning the transitive dependency's competing peer set, replaceOldDep() removes that shared node even though the project still requires it. Arborist then restores the node, requeues the project, and repeats the explicitly requested update.

Collect the nodes in peer sets entered from the project or a workspace before deciding which sets can move. Shared nodes in those sets remain in place with their forced conflict marked; exclusively transitive sets can still be pruned and resolved deeper in the tree.

The regression covers direct project/workspace dependencies, dependencies reached through another peer, and movable transitive groups, including an overlapping peer group. The four protected cases fail on the original implementation; the two transitive controls continue to pass.

Validation

Linux x64, Node.js 24.22.1, npm 12.2.1 source at 311ff5effbb1f96181c89a1ce6ebfe0c369e1ec1:

  • The issue's CLI sequence, using @typescript-eslint/[email protected], [email protected], and [email protected], then npm install eslint@9 --force --package-lock-only --ignore-scripts, did not finish within 45 seconds before the fix. With a fresh starting lockfile generated by the original implementation, the patched update completes in 4 seconds.
  • Placement tests and existing extension integration tests: 367 assertions pass across 3 files, with 100% statement, branch, function, and line coverage for lib/place-dep.js. The default coverage threshold remains enabled.
  • npm run lint in the Arborist workspace, including template-oss-check: passes.
  • Full Arborist suite: 5,151 assertions pass, 13 fail, and 3 are skipped across 59 files. The same 13 failures reproduce with the original production implementation: 11 assertions in legacy registry/engine fixtures fail on unavailable or unmatched registry requests, and 2 permission assertions expect EACCES when the test process runs as root.

Focused coverage command, from workspaces/arborist:

node ../../node_modules/tap/bin/run.js --coverage \
  --nyc-arg=--include=lib/place-dep.js --reporter=terse \
  test/place-dep.js test/arborist/package-extensions.js \
  test/arborist/reify-npm-extension.js

Autonomous contribution by ChatGPT (GPT-6 Pro).

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] installing a package with incompatible peer dependencies and --force sometimes results in an inf. loop

1 participant