Skip to content

[BUG] --install-links fails when local package has nested local dependencies #8342

Description

@u1f992

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

When installing a local package with --install-links, if that package has its own local dependencies, the installation fails. Is this behavior by design?

In this example, package b depends on package a, both located in sibling directories:

$ tree . -I node_modules
.
├── a
│   ├── package-lock.json
│   └── package.json
├── b
│   ├── package-lock.json
│   └── package.json
└── mainpkg
    ├── package-lock.json
    └── package.json

4 directories, 6 files

$ cat b/package.json 
{"name":"b","dependencies":{"a":"file:../a"}}

The npm CLI attempts to resolve ../a from within node_modules/b or node_modules/a, which does not exist. This results in an ENOENT error:

$ cd mainpkg && npm install --install-links ../b
npm error code ENOENT
npm error syscall open
npm error path /home/mukai/Documents/mainpkg/node_modules/a/package.json
npm error errno -2
npm error enoent Could not read package.json: Error: ENOENT: no such file or directory, open '/home/mukai/Documents/mainpkg/node_modules/a/package.json'
npm error enoent This is related to npm not being able to find a file.
npm error enoent
npm error A complete log of this run can be found in: /home/mukai/.npm/_logs/2025-06-03T11_28_04_990Z-debug-0.log

Expected Behavior

The dependency a, as listed in b's package.json, should be resolved relative to b itself (i.e., ../a from b). As a result, the structure of mainpkg/node_modules should look like this:

$ tree mainpkg/node_modules
mainpkg/node_modules
├── a -> ../../a
└── b -> ../../b

3 directories, 0 files

Steps To Reproduce

$ mkdir a b mainpkg
$ echo '{"name":"a"}' > a/package.json
$ echo '{"name":"b"}' > b/package.json
$ cd b && npm install ../a && cd ../
$ cd mainpkg && npm init --yes
$ npm install --install-links ../b

Environment

  • npm: 11.4.1
  • Node.js: v22.14.0
  • OS Name: Ubuntu 24.04
  • System Model Name: ThinkPad-X1-Carbon-7th
  • npm config:
; "user" config from /home/mukai/.npmrc

//registry.npmjs.org/:_authToken = (protected)

; node bin location = /home/mukai/.nvm/versions/node/v22.14.0/bin/node
; node version = v22.14.0
; npm local prefix = /home/mukai/Documents/mainpkg
; npm version = 11.4.1
; cwd = /home/mukai/Documents/mainpkg
; HOME = /home/mukai
; Run `npm config ls -l` to show all defaults.

Activity

  1. added
    Bugthing that needs fixing
    Needs Triageneeds review for next steps
    on Jun 3, 2025
  2. alexsch01 commented on Jun 3, 2025

    @alexsch01
    Contributor

    FYI: this issue is reproducible on npm versions before npm 11 too
    (I tested on npm 10.8.2)

  3. u1f992 commented on Jul 29, 2025

    @u1f992
    Author

    @owlstronaut Sorry, but I'm not entirely sure if PR #8436 actually resolves this issue. It’s true that npm install --install-links ../b now appears to succeed. However, once the code in package b tries to import a, won’t it still fail to find package a?

    For example:

    $ mkdir a && echo '{"name":"a","main":"index.js"}' > a/package.json && echo 'export const A = "A";' > a/index.js
    $ mkdir b && echo '{"name":"b","main":"index.js","dependencies":{"a":"file:../a"}}' > b/package.json && echo 'import {A} from "a";export const fn = () => console.log(A);' > b/index.js
    $ mkdir mainpkg && cd mainpkg && echo '{}' > package.json && npm install --install-links ../b # this will succeed properly
    
    $ node -e "const {fn}=await import('b');"
    node:internal/modules/package_json_reader:256
      throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
            ^
    
    Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'a' imported from /home/mukai/temp/mainpkg/node_modules/b/index.js
        at Object.getPackageJSONURL (node:internal/modules/package_json_reader:256:9)
        at packageResolve (node:internal/modules/esm/resolve:768:81)
        at moduleResolve (node:internal/modules/esm/resolve:854:18)
        at defaultResolve (node:internal/modules/esm/resolve:984:11)
        at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:780:12)
        at #cachedDefaultResolve (node:internal/modules/esm/loader:704:25)
        at ModuleLoader.resolve (node:internal/modules/esm/loader:687:38)
        at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:305:38)
        at ModuleJob._link (node:internal/modules/esm/module_job:175:49) {
      code: 'ERR_MODULE_NOT_FOUND'
    }
    

    Environment:

    $ node --version
    v22.17.0
    $ npm --version
    11.5.1
    
  4. owlstronaut commented on Jul 29, 2025

    @owlstronaut

    @u1f992 thanks for the example. I should be able to take another look at this next week

  5. self-assigned this
    on Jul 29, 2025
  6. owlstronaut commented on Aug 19, 2025

    @owlstronaut

    @u1f992 Could you give the same a try with 11.5.2. I'm not sure why, but it seems the changelog didn't pick up this fix. It looks to me like your example works in 11.5.2

  7. u1f992 commented on Aug 20, 2025

    @u1f992
    Author

    @owlstronaut Thank you for the follow-up. Unfortunately, the issue still seems to persist. As shown below, it looks like the same error occurs.

    $ npm --version
    11.5.2
    $ mkdir a && echo '{"name":"a","main":"index.js"}' > a/package.json && echo 'export const A = "A";' > a/index.js
    $ mkdir b && echo '{"name":"b","main":"index.js","dependencies":{"a":"file:../a"}}' > b/package.json && echo 'import {A} from "a";export const fn = () => console.log(A);' > b/index.js
    $ mkdir mainpkg && cd mainpkg && echo '{}' > package.json && npm install --install-links ../b
    mainpkg$ node -e "const {fn}=await import('b');"
    node:internal/modules/package_json_reader:256
      throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
            ^
    
    Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'a' imported from /home/mukai/temp/mainpkg/node_modules/b/index.js
        at Object.getPackageJSONURL (node:internal/modules/package_json_reader:256:9)
        at packageResolve (node:internal/modules/esm/resolve:768:81)
        at moduleResolve (node:internal/modules/esm/resolve:854:18)
        at defaultResolve (node:internal/modules/esm/resolve:984:11)
        at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:780:12)
        at #cachedDefaultResolve (node:internal/modules/esm/loader:704:25)
        at ModuleLoader.resolve (node:internal/modules/esm/loader:687:38)
        at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:305:38)
        at ModuleJob._link (node:internal/modules/esm/module_job:175:49) {
      code: 'ERR_MODULE_NOT_FOUND'
    }
    
    Node.js v22.17.0
    
  8. u1f992 commented on Nov 21, 2025

    @u1f992
    Author

    @owlstronaut Thank you for the fix! This indeed solved my issue.

    By the way, is it by design that transitive external file dependencies are only handled when --install-links is used?

    The original purpose of --install-links is to install file: protocol dependencies by copying instead of linking. That’s why, with the patch you applied this time, the packages are installed like this:

    $ npm install --install-links ../b
    $ tree node_modules/
    node_modules/
    ├── a
    │   ├── index.js
    │   └── package.json
    └── b
        ├── index.js
        └── package.json
    
    3 directories, 4 files
    

    On the other hand, without --install-links, only b is still installed as a symbolic link. Naturally, in this case, importing b will fail.

    $ npm install ../b
    node_modules/
    └── b -> ../../b
    
    2 directories, 0 files
    $ node -e "const {fn}=await import('b');"
    node:internal/modules/package_json_reader:266
      throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
            ^
    
    Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'a' imported from /home/mukai/Documents/vivliostyle-cli-test/npmcli-tmp/b/index.js
        at Object.getPackageJSONURL (node:internal/modules/package_json_reader:266:9)
        at packageResolve (node:internal/modules/esm/resolve:767:81)
        at moduleResolve (node:internal/modules/esm/resolve:853:18)
        at defaultResolve (node:internal/modules/esm/resolve:983:11)
        at #cachedDefaultResolve (node:internal/modules/esm/loader:731:20)
        at ModuleLoader.resolve (node:internal/modules/esm/loader:708:38)
        at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:310:38)
        at ModuleJob._link (node:internal/modules/esm/module_job:183:49) {
      code: 'ERR_MODULE_NOT_FOUND'
    }
    
    Node.js v22.20.0
    

    It seems like npm could just create a symbolic link to ../a from ../../b even without --install-links. Something like this:

    node_modules/
    ├── a -> ../../a
    └── b -> ../../b
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Bugthing that needs fixingNeeds Triageneeds review for next steps

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions