#11145·axios

`AxiosError.prototype.toJSON()` can OOM when config includes an `httpAgent`/`httpsAgent`

Author: skrthebossCreated Aug 12, 2026Updated Aug 25, 2026

Problem

Calling error.toJSON() on an AxiosError (e.g. from an error logger such as pino, winston, or Sentry) can cause unbounded memory growth and crash the process with JavaScript heap out of memory when the request config includes a Node http.Agent/https.Agent, even under moderate concurrency.

This is a realistic, common configuration — using a shared httpAgent/httpsAgent for connection reuse is a widely recommended pattern — so any failed request logged through a standard error logger can trigger it.

Reproduction

import http from 'node:http';
import https from 'node:https';
import axios from 'axios';

const server = http.createServer((req, res) => {
  res.writeHead(500, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ message: 'duplicate key error' }));
});

server.listen(0, async () => {
  const { port } = server.address();
  const instance = axios.create({
    httpAgent: new http.Agent(),
    httpsAgent: new https.Agent(),
    timeout: 5000,
  });

  await Promise.all(Array.from({ length: 25 }, () =>
    instance.get(`http://127.0.0.1:${port}/`).catch((error) => {
      error.toJSON(); // realistic call site: any error logger
    })
  ));
  server.close();
});

Run with:

node --max-old-space-size=512 repro.mjs

Result:

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

Environment

  • axios 1.2.0 through current (verified against latest v1.x)
  • Node.js (reproduced on v26.7.0; the Agent/Socket object shape causing this is stable across Node versions)

Early Copilot findings / suggestions

The following analysis was produced with AI assistance to help maintainers get started faster. It has not been independently verified by a human and should be treated as a starting point, not a conclusion.

Suspected root cause

AxiosError.prototype.toJSON() calls utils.toJSONObject(this.config) unconditionally. toJSONObject's cycle detection is ancestor-path-only (it tracks the current recursion path, not a global "already visited" set). This appears intentional — it preserves correct serialization of shared sibling/DAG references instead of silently dropping them. The tradeoff: any object reachable via multiple distinct paths gets fully re-serialized once per path.

Node's http(s).Agent / Socket / TLSSocket internals appear to have exactly this shape — heavily cross-referenced (agent ↔ sockets ↔ parser ↔ request ↔ response ↔ socket). Serializing config.httpAgent/httpsAgent therefore walks and re-walks large, overlapping subgraphs, and the cost seems to compound with concurrent in-flight requests sharing one agent.

Bisect

Bisecting against the repro above points to the origin as commit b7ee49f6 (PR #5247, "Added toJSONObject util", merged 2022-11-22, first shipped in axios 1.2.0), which changed AxiosError.prototype.toJSON() from:

config: this.config

to:

config: utils.toJSONObject(this.config)

i.e. from a shallow reference to a recursive deep-clone. The repro passes cleanly against the parent commit (a372b4ce) and OOMs immediately on b7ee49f6 itself, with the same script and same --max-old-space-size limit.

Also checked: the later refactor in PR #10832 ("use WeakSet for cycle detection in toJSONObject") does not appear to be related. That PR only swapped the cycle-detection data structure (Array stack → WeakSet) while preserving identical ancestor-path-only semantics. The repro crashes equally (same magnitude, same threshold) on commits immediately before and after #10832.

Possible directions (unverified, for discussion)

  • Don't deep-serialize httpAgent/httpsAgent (and similar platform handles) in AxiosError.toJSON() — replace with a shallow marker (e.g. '[http.Agent]') rather than recursing into Node internals.
  • Add a depth or node-count budget to toJSONObject to bound worst-case work regardless of graph shape.
  • Consider excluding known Node core objects (net.Socket, tls.TLSSocket, http.Agent, http.IncomingMessage, http.ClientRequest) from recursive serialization by default.