npm / npm/cli

npm ls marks satisfied edges invalid when sibling override rulesets disagree about an unrelated, not-installed package

Open
#9,996 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
10.1k
Forks
4.7k
Avg merge
2d 2h
Merged PRs (30d)
19

Description

Summary

npm ls --all reports a dependency edge as invalid even though the target version satisfies the edge's range, when:

  • the edge's override set is the rule for a root-level overridden package (package A),
  • the target node's override set is the rule for a different root-level scoped override (package B),
  • A's rule and B's rule are siblings under the root override set, and
  • the collapsed rulesets of the two siblings disagree about the version of some third package — one side sees the root's global override value, the other side sees a scoped override value that shadows it.

In the repro below the third package (js-yaml) is not installed anywhere in the tree, and no override rule references either the edge's from or to package (http-proxy-agentdebug).

Environment
  • Node v22.22.0, Linux
  • Affected: npm 11.2.0 → 11.19.1 (arborist 9.0.1 → 9.9.1) and npm 12.0.2 (arborist 10.0.2)
  • Not affected: npm 11.1.0 (arborist 9.0.0) and npm 10.9.9
  • Introduced by arborist 9.0.1 (shipped in npm 11.2.0) via #8089 (commit b9225e52), which added the doOverrideSetsConflict INVALID branch to Edge#get error()
Reproduction

package.json:

{
  "name": "override-ruleset-repro",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "http-proxy-agent": "^7.0.0",
    "@babel/core": "^7.29.7"
  },
  "overrides": {
    "js-yaml": "5.3.0",
    "@babel/core": "^7.29.7",
    "http-proxy-agent": {
      "js-yaml": "4.3.2"
    }
  }
}

Build the lockfile with an unaffected npm, then re-install and list with a current npm:

$ npm@10.9.9 install
$ npm@11.19.1 ci
$ npm@11.19.1 ls --all
...
│ ├─┬ debug@4.4.3 invalid: "^4.3.4" from node_modules/http-proxy-agent
│ │ └── ms@2.1.3
...
npm error code ELSPROBLEMS
npm error invalid: debug@4.4.3 .../node_modules/debug

debug@4.4.3 satisfies ^4.3.4. Running npm@10.9.9 ls --all against the same node_modules and lockfile exits 0 with no invalid edges — only the npm version differs.

Why it happens

With npm 11.19.1's bundled arborist (9.9.1):

  • workspaces/arborist/lib/edge.js line 278 marks the edge INVALID when doOverrideSetsConflict(edge.overrides, target.overrides) returns true.
  • The edge from http-proxy-agent carries the override set http-proxy-agent=* > ROOT; the hoisted debug node carries @babel/core=^7.29.7 > ROOT (assigned from the @babel/core placement). These rulesets are siblings, so findSpecificOverrideSet() finds no containment in either direction and falls through to haveConflictingRules().
  • haveConflictingRules() compares first.ruleset vs second.ruleset. The ruleset getter collapses the whole ancestry (self first, so a scoped child shadows the root value for the same key):
    • @babel/core's collapsed set contains js-yaml -> 5.3.0 (the root global override),
    • http-proxy-agent's collapsed set contains js-yaml -> 4.3.2 (its own scoped rule shadows the root's),
    • semver.intersects('5.3.0', '4.3.2') is false → conflict declared → edge INVALID.

Neither ruleset contains a rule for debug, and the two js-yaml rules can never apply to the same package instance (one is scoped to http-proxy-agent's subtree), so this is a false positive for the http-proxy-agent → debug edge specifically.

Programmatic probe (arborist 9.9.1, loadActual() on the repro):

debug@4.4.3:
  node.ov chain : @babel/core=^7.29.7 > ROOT
  node.ov rules : @babel/core->^7.29.7, http-proxy-agent->*, js-yaml->5.3.0
  js-yaml installed anywhere: NO

edge ^4.1.0 from @babel/core:      edge.ov=@babel/core=^7.29.7 > ROOT  -> OK
edge ^4.3.1 from @babel/traverse:  edge.ov=@babel/core=^7.29.7 > ROOT  -> OK
edge ^4.3.4 from http-proxy-agent: edge.ov=http-proxy-agent=* > ROOT   -> INVALID
Workarounds

Removing either the global js-yaml override or the scoped http-proxy-agent → js-yaml override makes the edge valid again — but both exist for real reasons in the original project (global security pin + a scoped legacy pin). Our CI currently has to tolerate non-zero npm ls --all exit codes.

Suggested direction

haveConflictingRules (or findSpecificOverrideSet) could restrict the comparison to rules that can actually apply to the edge's target — for example only declaring a conflict when a rule for the target package's name (or for a package in its dependency closure) differs between the two sets. Comparing the full collapsed rulesets of sibling subtrees compares rules that are mutually exclusive by construction.

Related
  • #7087 — invalid edges with overrides (different trigger)
  • #9514 — overrides within workspaces
  • #4834 — overrides not applied with workspaces

This report differs in that no workspaces are involved and the "conflicting" override key refers to a package that is not installed at all.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the report with the supplied package.json and npm install/ci/ls commands, then inspect workspaces/arborist/lib/edge.js around line 278, especially haveConflictingRules and findSpecificOverrideSet. Trace how sibling override rules are compared and verify that unrelated, mutually exclusive rules do not invalidate the http-proxy-agent → debug edge while genuine conflicts remain detectable.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, nodejs
Domain
cli, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.