npm / npm/cli

[BUG] `install` with `--install-links` and local path dependencies "dedups" packages even when the local path is different

Open
#8,364 10 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Is there an existing issue for this?
  • I have searched the existing issues
This issue exists in the latest npm version
  • I am using the latest npm
Current Behavior

Using a local registry, where all package versions use local paths, and installing with --install-links, packages that are referenced multiple times get deduped even though they are different versions (in this case, have a different local path).

Consider package copy-props 2.0.5.

This package has the following truncated dependency tree, where there are two versions of is-plain-object used, 2.0.4 and 5.0.0:

copy-props (2.0.5)
├── each-props (1.3.2)
│   ├── is-plain-object (2.0.4)
│   └── ...
└── is-plain-object (5.0.0)

Normally this has the following tree when installed via the NPM registry, where you can see both version 2.0.4 and 5.0.0 of is-plain-object present in node_modules:

.
├── node_modules
│   ├── array-each
│   │   └── ...
│   ├── array-slice
│   │   └── ...
│   ├── copy-props
│   │   └── ...
│   ├── each-props
│   │   ├── node_modules
│   │   │   └── is-plain-object <------------- 2.0.4
│   │   │       └── ...
│   │   └── ...
│   ├── for-in
│   │   └── ...
│   ├── for-own
│   │   └── ...
│   ├── isobject
│   │   └── ...
│   ├── is-plain-object <------------------- 5.0.0
│   │   └── ...
│   └── object.defaults
│       └── ...
└── ...

However, if you create your own local registry with all of these packages available, and use local paths to define the package version in the list of dependencies in package.json, and if you use --install-links option, the resulting node_modules structure is as follows, where it's missing version 2.0.4 of is-plain-object:

.
├── node_modules
│   ├── array-each
│   │   └── ...
│   ├── array-slice
│   │   └── ...
│   ├── copy-props
│   │   └── ...
│   ├── each-props <- ⚠️ no inner node_modules, no is-plain-object 2.0.4
│   │   └── ...
│   ├── for-in
│   │   └── ...
│   ├── for-own
│   │   └── ...
│   ├── isobject
│   │   └── ...
│   ├── is-plain-object <-------------------------- 5.0.0
│   │   └── ...
│   └── object.defaults
│       └── ...
└── ...

In the above example, here is an excerpt from various package.json files in the local registry:

local-repo/copy-props/package.json:

{
  "dependencies": {
    "each-props": "/path/to/local-repo/each-props",
    "is-plain-object": "/path/to/local-repo/is-plain-object@5"
  }
}

local-repo/each-props/package.json:

{
  "dependencies": {
    "is-plain-object": "/path/to/local-repo/is-plain-object@2",
    "object.defaults": "/path/to/local-repo/object.defaults"
  }
}

If the user does NOT use --install-links, both versions 2.0.4 and 5.0.0 are installed.

Expected Behavior

Regardless if using --install-links or not, when using npm install with a local registry using local paths, the output node_modules files structure should be the same (except with installed files instead of symlinks).

Steps To Reproduce
  1. Download, unzip, and chdir into test.zip
  2. Delete broken/node_modules/ and broken/package-lock.json
  3. In all of the 11 broken/**/package.json files (including broken/package.json) replace all occurances of C:/Users/Dan/test/broken with $(pwd)/broken
  4. cd into broken and run npm --install-links install
  5. Navigate to the newly created node_modules and notice the missing is-plain-object version 2.0.4
Environment
  • npm: 11.3.0
  • Node.js: v24.2.0
  • OS Name: Windows 10
  • System Model Name:
  • npm config:
; node bin location = C:\Program Files\nodejs\node.exe
; node version = v24.2.0
; npm local prefix = C:\Users\Dan\test\broken
; npm version = 11.3.0
; cwd = C:\Users\Dan\test\broken
; HOME = C:\Users\Dan

This also happens on Guix System (Linux) with NPM 10.9.2

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

Start with the package.json dependency paths and reproduce the install using the provided test.zip and npm --install-links install. Compare the node_modules trees produced with and without --install-links, focusing on the missing is-plain-object 2.0.4 dependency. Done means both modes retain dependencies from distinct local paths while installing linked files rather than symlinks.

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
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.