Skip to content
Latchkey

Node.js "read ECONNRESET" - Flaky HTTP Test in CI

The remote side reset the TCP connection while Node was reading from it. In CI this is a classic flaky-test failure - a keep-alive socket closed, a proxy dropped the connection, or the network blipped.

What this error means

An HTTP test intermittently fails with read ECONNRESET. Re-running the same job usually passes, which is the signature of a transient connection reset rather than a real bug.

node
Error: read ECONNRESET
    at TCP.onStreamRead (node:internal/stream_base_commons:217:20)
  errno: -104,
  code: 'ECONNRESET',
  syscall: 'read'

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

The peer closed a keep-alive connection

A server or proxy closed an idle keep-alive socket just as the client reused it, producing a reset mid-read. This is intermittent.

A transient network reset

A momentary network disruption between the runner and the host resets the connection. It is not deterministic.

How to fix it

Retry transient resets

Wrap the request in a bounded retry that treats ECONNRESET as retryable.

test/helpers.ts
async function withRetry(fn, tries = 3) {
  for (let i = 0; i < tries; i++) {
    try { return await fn(); }
    catch (e) { if (e.code !== 'ECONNRESET' || i === tries - 1) throw e; }
  }
}

Disable keep-alive reuse in tests

Use a fresh connection per test request so a stale keep-alive socket cannot be reset under you.

test/http.ts
const res = await fetch(url, { headers: { Connection: 'close' } });

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

  • Add bounded retries for ECONNRESET in network-bound tests.
  • Avoid reusing keep-alive sockets across flaky test boundaries.
  • Isolate real-network tests so resets do not fail unrelated suites.

Frequently asked questions

What causes Node.js "read ECONNRESET"?
There are 2 common causes: the peer closed a keep-alive connection and a transient network reset. A server or proxy closed an idle keep-alive socket just as the client reused it, producing a reset mid-read.
How do I fix Node.js "read ECONNRESET"?
There are 2 fixes depending on which cause you have: retry transient resets and disable keep-alive reuse in tests. Work through them in order, since the first is the most common.
What does Node.js "read ECONNRESET" actually mean?
An HTTP test intermittently fails with read ECONNRESET.
How do I stop Node.js "read ECONNRESET" happening again?
Add bounded retries for ECONNRESET in network-bound tests. 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