npm install stuck on idealTree buildDeps: find the stall
Reproduced on npm 10.9.9 against a server that accepts the connection and never replies: what each log level shows, why fetch-timeout does not end the wait, and the one-line fix.
TL;DR#
npm install stops on idealTree buildDeps when the very first network request it makes, usually the metadata for one of your dependencies, never gets an answer. npm keeps waiting without printing anything at the default log level. Run the install again with --loglevel silly: the last line names the URL npm is waiting on. Test that host with curl --connect-timeout 15, then fix what sits between you and it. Common culprits are a stale https-proxy or registry line in an .npmrc, a private registry you can no longer reach, or a broken network path. Raising or lowering fetch-timeout does not end the wait when the server accepts the connection but never replies.
The symptom, as quoted in the npm/cli issue #3359 (open since June 2021, 54 reactions) from npm 7's progress bar:
[....................] \ idealTree.pql-box: still idealTree buildDepsWhat npm is doing at that step#
idealTree is the phase where npm works out which package versions it will install. buildDeps is the part that fetches each dependency's metadata (its packument) from the registry, or the tarball of a git dependency. Nothing can be resolved until those answers arrive, so a request that never completes stalls the whole command right there. One commenter on the issue measured about seven minutes of silence before npm gave up when registry.npmjs.org could not be reached. Later comments report other ways into the same stall:
- IPv6: dual-stack connections where IPv6 was broken or had to be set to link-local only on macOS.
- Self-signed certificate: a private Nexus registry behind one.
- Empty
cavalue: an emptyca = ""line in a user.npmrc.
These are reports from individual users, not diagnoses from npm. What they share is that the network answer never reaches npm. That is the part you can check yourself.
Why the timeouts do not save you#
npm's config definitions set fetch-retries to 2 and fetch-timeout to 5 minutes. The 5-minute value is described there as "The maximum amount of time to wait for HTTP requests to complete." The HTTP agent npm uses, @npmcli/agent, does not apply it as a total limit. It maps the value to an idle timeout and sets its own connection timeout to zero. These are the defaults in its timeouts object, unchanged between the copy bundled with npm 10.9.9 and the current main branch:
idle: opts.timeout ?? 0,
connection: 0,
response: 0,
transfer: 0,In agents.js the idle timer is only armed after the connection is established. For a TLS registry, that means once the TLS handshake has finished. A connection that is accepted and then left silent is never established in that sense, so that timer never starts. The fetch layer above it, minipass-fetch, starts its own request timer only when the request is handed a socket. In the reproduction below, neither ended the wait. A refused or reset connection is different: it ends in an error, and npm retries it.
Reproduce the stall#
This was reproduced on 20 October 2026 (UTC) in Docker Desktop with the node:22-slim image (Node 22.23.3, npm 10.9.9). A second container on a private network accepts TCP on port 443 and never sends a byte. registry.npmjs.org was pointed at it with --add-host, and the project had one dependency, is-number. With --loglevel silly, the output stops on the request for that package:
npm silly idealTree buildDeps
npm silly fetch manifest is-number@7.0.0
npm silly packumentCache full:https://registry.npmjs.org/is-number cache-miss
elapsed: 120sThe 120 seconds is when timeout killed npm, not an error. Two more runs show how long the wait lasts:
- Default settings: an install was still silent after about 11 minutes.
- With
--fetch-timeout=10000 --fetch-retries=0: an install was still silent after 8 minutes 50 seconds.
Both runs were stopped by hand. The full output is in the repro log for this article.
The same hang appears without touching DNS when the project .npmrc sends traffic to a proxy that never answers. At --loglevel verbose, npm printed its log-file lines and then nothing for two minutes:
npm verbose logfile /tmp/empty-cache/_logs/2026-10-20T23_25_40_828Z-debug-0.log
elapsed: 120sFix#
- Rerun the install with the most detailed log level, and read the last lines it prints before going quiet:
npm install --loglevel sillyA line ending in cache-miss with no response after it names the URL npm is waiting on. If the connection is being refused rather than ignored, --verbose already shows it, one line per attempt. This is from the same container, with the registry address refusing connections:
npm http fetch GET https://registry.npmjs.org/is-number attempt 1 failed with ECONNREFUSED
npm http fetch GET https://registry.npmjs.org/is-number attempt 2 failed with ECONNREFUSEDAt the default log level, that same run printed nothing at all for 60 seconds.
- Test that host outside npm. curl's connect timeout covers the TLS handshake too, so a silent server makes it fail instead of hang:
curl --connect-timeout 15 https://registry.npmjs.orgAgainst the silent server it stopped after 15 seconds with:
curl: (28) SSL connection timeoutA working registry answers with JSON at once. If curl also fails, the problem is the network path, not npm. That is the test suggested in the issue.
- Check where npm is actually sending its requests. A proxy or registry line from an old
.npmrc, whether in the project, in your home directory or in CI, overrides what you think npm is using:
npm config get registry
npm config get https-proxy
npm config get proxyIn the reproduction, npm config get https-proxy printed http://172.19.0.2:443, the silent server.
- Remove the line that points npm at a dead host. For a line in the project
.npmrc:
npm config delete https-proxy --location=projectIf the line sits in ~/.npmrc or a CI-provided .npmrc, edit that file instead. If the registry itself is unreachable from this network, that is something to fix in the network, the VPN or the private registry, not in npm.
Verify the fix#
Run the install again. In the reproduction, after the proxy line was deleted, npm config get https-proxy printed null and the install finished straight away:
added 1 package, and audited 2 packages in 758msA completed install prints the added … packages summary. To see each request with its status and time, add --loglevel http. Every npm http fetch GET 200 line is a request that got an answer.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
The original case: a GitLab git dependency#
The issue's title describes a narrower case. In a project with nothing but a git+ssh dependency on GitLab, npm install took over 60 seconds on npm 7, against 2 to 3 seconds on npm 6. A maintainer traced it to npm first trying to download a tarball of the repository from GitLab. That request failed (the maintainer got a 503 from it with wget) and npm waited "too long before just falling back to cloning the repo". The proposed change to GitLab's URL template, hosted-git-info PR #109, was closed without being merged. npm treats a 5xx answer as retryable: its config definitions say it "will retry idempotent read requests to the registry in the case of network failures or 5xx HTTP errors", and the retry test in make-fetch-happen applies to any non-POST request with a 5xx status, whatever the host. The maintainer's comment does not break the delay down attempt by attempt, but this case is a server that answered with an error, not a silent one.
Running the issue's exact package.json (no lock file, empty cache) on npm 10.9.9 in node:22-bookworm on 20 October 2026 (UTC) no longer reproduces the delay. GitLab now answers the tarball request with a 404 straight away, and npm falls back:
npm http fetch GET 404 https://gitlab.com/klesun/npm-seven-issue-dep/repository/archive.tar.gz?ref=5f1b313978986974d4364585e6bd3689276f1697 537ms (cache skip)
added 1 package, and audited 2 packages in 6sIf a git dependency on a self-hosted GitLab is still slow, use --loglevel http to see what happens to the archive URL: either it fails with a 5xx, which npm retries before falling back to git, or it never answers at all, which is the silent stall described above. The log line tells you which of the two you are dealing with before you change anything.
Once the install gets past buildDeps#
Once the registry answers, the install moves on to resolving and writing the tree, and different errors take over. A peer range that cannot be satisfied stops it with ERESOLVE, as in this Firebase prerelease conflict. If an earlier sudo npm left root-owned files in ~/.npm, the next stop is EACCES on the cache folder. On npm 6, a dependency whose package.json carries a version such as "1" fails with Invalid version. The hang itself is never an npm resolution problem: it is a request that went out and got no reply. Find which request it is, and the fix is usually a single .npmrc line or a network path that needs repairing.
Further reading: Fix Invalid options object: allowedHosts[0] with CRA proxy.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix Docker npm EBADENGINE: Unsupported engine
Read the Required and Actual lines, distinguish npm self-upgrades from application installs, and stop a floating npm release breaking an unchanged Dockerfile.
Fix npm WARN package.json: No repository field
The warning is informational and harmless. Silence it for good in an application, fill it in properly for a library, and know why modern npm no longer prints it.
npm WARN requires a peer of X but none is installed: fix
Read the peer range npm prints, install it into your app with one command, and see why the same project installs cleanly on npm 7 and later.


