Skip to content
Latchkey

Node.js "TypeError: fetch failed" (undici) in CI

Node's global fetch (powered by undici) wraps low-level failures in a terse TypeError: fetch failed. The real reason is in the cause chain - usually a transient DNS, connection, or TLS problem.

What this error means

A request made with the built-in fetch rejects with TypeError: fetch failed. The same call works locally and often passes when the job is retried, pointing at a flaky network rather than a code bug.

node
TypeError: fetch failed
    at node:internal/deps/undici/undici:13392:13
  cause: Error: connect ECONNREFUSED 127.0.0.1:443
    code: 'ECONNREFUSED'

Diagnose it: what is different about the runner?

A build that passes locally and fails on a runner differs in a small number of predictable ways. Check those before changing build configuration, because the build config is usually not the thing that changed.

.github/workflows/ci.yml
- run: |
    node --version && npm --version
    echo "NODE_ENV=$NODE_ENV  CI=$CI"
    nproc && free -h && df -h /
    ls -la node_modules/.bin | head

Common causes

Transient network or DNS failure

A dropped connection, slow DNS, or a momentary registry/API blip surfaces as fetch failed. These are intermittent and pass on retry.

A service or proxy not reachable from the runner

The target host is firewalled, the proxy env is missing, or a local server under test is not up yet, so the connection is refused.

How to fix it

Inspect err.cause and retry transient failures

Log err.cause to see the underlying code, then wrap the request in a bounded retry for transient classes.

src/http.js
try {
  const res = await fetch(url);
} catch (err) {
  console.error('fetch failed cause:', err.cause);
  throw err;
}

Ensure the dependency is reachable

Confirm DNS/proxy settings and that any local service the test calls is started before the request runs.

  1. Check HTTP(S)_PROXY/NO_PROXY env in the runner.
  2. Wait for local servers to be ready before issuing requests.
  3. Verify the host resolves and is allowlisted from the runner.

The three that account for most of them

  • Case sensitivity. Linux runners are case sensitive, macOS is not. An import with the wrong case resolves locally and fails in CI.
  • Out of memory. Exit code 137 is a SIGKILL from the kernel, not a build error. Raise --max-old-space-size or use a larger runner.
  • devDependencies pruned. NODE_ENV=production makes npm ci skip devDependencies, so the build tool itself goes missing. Set it after install, not before.

How to prevent it

  • Always read err.cause to classify the underlying failure.
  • Add bounded retries around outbound calls for genuinely transient classes.
  • Wait for dependent services to be ready before making requests.

Frequently asked questions

What causes Node.js "TypeError: fetch failed" (undici) in CI?
There are 2 common causes: transient network or dns failure and a service or proxy not reachable from the runner. A dropped connection, slow DNS, or a momentary registry/API blip surfaces as fetch failed.
How do I fix Node.js "TypeError: fetch failed" (undici) in CI?
There are 2 fixes depending on which cause you have: inspect err.cause and retry transient failures and ensure the dependency is reachable. Work through them in order, since the first is the most common.
What does Node.js "TypeError: fetch failed" (undici) in CI actually mean?
A request made with the built-in fetch rejects with TypeError: fetch failed.
How do I stop Node.js "TypeError: fetch failed" (undici) in CI happening again?
Always read err.cause to classify the underlying failure. The prevention section lists 3 changes that keep it from recurring.
Can Latchkey fix this automatically?
Yes. Latchkey runs your GitHub Actions on managed runners that detect this failure, apply the fix, and retry the job automatically - self-healing is on by default.

Related guides

References

This is a transient network failure, not a bug in your code. Latchkey detects, repairs, and retries it for you. Start free → 30-day trial · No credit card