Skip to content

fix(arborist): validate peerOptional conflicts in no-save mutations#9605

Merged
owlstronaut merged 1 commit into
npm:latestfrom
dale-lakes:codex/fix-update-peeroptional-lockfile
Jun 24, 2026
Merged

fix(arborist): validate peerOptional conflicts in no-save mutations#9605
owlstronaut merged 1 commit into
npm:latestfrom
dale-lakes:codex/fix-update-peeroptional-lockfile

Conversation

@dale-lakes

@dale-lakes dale-lakes commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Problem

Arborist currently treats save=false as a signal that invalid peerOptional edges from the lockfile can be trusted and skipped. That is correct for non-mutating lockfile reads such as npm ci, but it is wrong for commands that still mutate the ideal tree and write a new lockfile while leaving package.json unchanged.

For example, npm update runs with save=false, but it can still update package versions in package-lock.json. If one of those updates introduces or exposes an invalid optional peer relationship, Arborist can finish successfully and write a lockfile that a later plain npm install rejects with ERESOLVE.

Fix

This separates two concepts that were previously conflated:

  • #requestedTreeMutation: whether the command explicitly requested an add, remove, or update operation.
  • #mutateTree: whether the ideal-tree build has actually changed the tree during placement.

Invalid peerOptional policy now uses #requestedTreeMutation instead of #mutateTree:

  • save=false + explicit add/rm/update: invalid peerOptional edges are treated as resolver problems.
  • save=false + no requested mutation: invalid peerOptional edges remain trusted, preserving the existing npm ci-style behavior.

The requeue path also uses #requestedTreeMutation, so a save=false update can reprocess an already-seen node when a later placement invalidates one of its peerOptional edges.

Behavior Changed

This fixes bad lockfile output for save=false commands that mutate the dependency graph, including:

  • npm update
  • npm install <pkg> --no-save
  • other explicit add/rm/update Arborist builds with save=false

Those commands should not silently produce an ideal tree or lockfile containing invalid peerOptional edges that a subsequent install rejects.

Behavior Preserved

Non-mutating save=false builds continue to trust the lockfile. In those cases, an invalid peerOptional edge is still ignored rather than forcing re-resolution, preserving the behavior needed by npm ci-style lockfile reads.

Fixes #9604.

Tests

Added regression coverage for:

  • npm update-style save=false mutation rejecting an invalid peerOptional graph instead of writing a bad lockfile.
  • npm install <pkg> --no-save-style mutation rejecting the same class of invalid graph.
  • save=false update requeueing an already-seen node when later placement invalidates its peerOptional edge.
  • non-mutating save=false builds continuing to ignore invalid peerOptional problem edges.

Validated with:

git diff --check
npm exec -- tap # from workspaces/arborist
npm run lint --workspace=@npmcli/arborist

@dale-lakes dale-lakes force-pushed the codex/fix-update-peeroptional-lockfile branch 2 times, most recently from d013a56 to 566b700 Compare June 23, 2026 16:44
@dale-lakes dale-lakes changed the title fix(arborist): validate peerOptional conflicts during update fix(arborist): validate peerOptional conflicts in no-save mutations Jun 23, 2026
@dale-lakes dale-lakes force-pushed the codex/fix-update-peeroptional-lockfile branch from 566b700 to fd519a0 Compare June 23, 2026 17:04
@dale-lakes dale-lakes marked this pull request as ready for review June 23, 2026 17:15
@dale-lakes dale-lakes requested review from a team as code owners June 23, 2026 17:15
@dale-lakes dale-lakes force-pushed the codex/fix-update-peeroptional-lockfile branch from fd519a0 to 0354ba9 Compare June 23, 2026 17:24
@owlstronaut owlstronaut merged commit 2aa1c7c into npm:latest Jun 24, 2026
25 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Backport to release/v11 failed.

This usually means the cherry-pick had conflicts. Please create a manual backport:

git fetch origin release/v11
git checkout -b backport/v11/9605 origin/release/v11
git cherry-pick -x 2aa1c7cd25399c49f12aab684341cc1743ad1635
# resolve any conflicts, then:
git push origin backport/v11/9605
Error details
Command failed: git cherry-pick -x 2aa1c7cd25399c49f12aab684341cc1743ad1635
error: could not apply 2aa1c7cd2... fix(arborist): validate peerOptional conflicts in no-save mutations (#9605)
hint: After resolving the conflicts, mark them with
hint: "git add/rm <pathspec>", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
hint: Disable this message with "git config set advice.mergeConflict false"

owlstronaut added a commit that referenced this pull request Jun 24, 2026
…9641)

Backport of #9605 to `release/v11`.

Co-authored-by: Dale Lakes <6843636+dale-lakes@users.noreply.github.com>
Co-authored-by: Dale Lakes <6843636+spitfire55@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] npm update can write a lockfile that npm install rejects

2 participants