Open Source
Axios

Published: August 15, 2026 · by Srinu Desetti · axios/axios#10913 (opens in a new tab)

How I Fixed Error Serialization Crashes in Axios

Axios is used by millions of developers worldwide. My contribution fixed a bug in its fetch adapter where logging a network error could crash the application with TypeError: Converting circular structure to JSON.

What Was the Issue?

The bug was not that Axios created a circular reference.

The circular reference already existed inside some Node.js fetch network errors. Axios accidentally exposed that circular object by making the cause property enumerable.

Where Did the Circular Reference Come From?

When a network request fails (for example ECONNREFUSED or a DNS failure), Node.js creates an internal error object. That error may reference internal objects such as the socket, request, stream, and parser — and those internal objects may reference each other:

Socket


Request


Socket        ← back to where we started: a cycle

The Old Axios Code

lib/adapters/fetch.js — before
throw Object.assign(
  new AxiosError("Network Error"),
  {
    cause: err.cause || err
  }
);

Object.assign() creates enumerable properties. So the AxiosError became:

AxiosError
├── message
├── code
└── cause   (enumerable ← the problem)

What does enumerable mean?

Enumerable means JavaScript includes the property when iterating:

Object.keys(error)
for (const k in error) {}
JSON.stringify(error)
{ ...error }

All of these walk enumerable properties.

Why Did Logging Crash?

Loggers like Pino and Winston serialize errors by walking their enumerable properties:

AxiosError → cause → originalError → socket → request → socket → …

The logger keeps traversing the circular references and eventually throws:

TypeError: Converting circular structure to JSON

The logger crashes — not because Axios created a circular reference, but because it was allowed to walk into one.

The Fix

Replace Object.assign with Object.defineProperty, making cause non-enumerable — the same behavior as JavaScript's native Error:

lib/adapters/fetch.js — after (my fix)
Object.defineProperty(networkError, "cause", {
  value: err.cause || err,
  writable: true,
  enumerable: false,
  configurable: true
});

Now cause still exists, but is hidden from iteration:

AxiosError
├── message
├── code
└── cause (non-enumerable ← hidden from loggers)

What Changed for Users

Direct access still works — debugging is fully preserved:

error.cause          // ✅ still works
error.cause.code     // ✅ still works

But automatic traversal skips it:

Object.keys(error)      // no cause
JSON.stringify(error)   // no crash

The logger never reaches the circular Node.js internals.

Important clarification — the fix did not remove the circular reference. It still exists inside the original Node.js error. The fix only prevents serializers from automatically traversing into it, matching native Error behavior.

An Analogy

Think of a house with a hall, a kitchen, and a secret room containing a dangerous machine. A cleaning robot enters every room and gets stuck in the secret room. The fix doesn't remove the machine — it hides the door from the robot. The robot cleans the visible rooms and never gets stuck.

Impact

  • Applications using Axios's fetch adapter no longer crash while logging network errors — a failure path that previously turned "the network blipped" into "the process threw during error handling".
  • Structured loggers (Pino, Winston) serialize AxiosErrors safely out of the box.
  • Debugging is unchanged: error.cause remains fully accessible, now consistent with how native JavaScript Error treats cause.

What I Contributed

  • Identified the root cause: an enumerable cause exposing Node's circular error internals.
  • Implemented the fix with Object.defineProperty.
  • Wrote comprehensive test cases.
  • Collaborated with the Axios maintainers through review until the PR was merged.

View the pull request → axios/axios#10913 (opens in a new tab)


← All contributions · Next: React Redux error reporting →


Frequently Asked Questions

Did Axios create the circular reference?

No. The circular reference already existed inside Node.js fetch network errors, which internally reference the socket, request, and parser objects that reference each other. Axios only exposed it by attaching the error as an enumerable cause property.

Did the fix remove the circular reference?

No. The circular structure still exists inside the original Node.js error. The fix makes the cause property non-enumerable, so serializers and loggers skip it during iteration while direct access via error.cause keeps working.

Why did loggers like Pino and Winston crash?

They serialize errors by walking enumerable properties. With cause enumerable, they traversed into the circular Node.js internals and threw TypeError: Converting circular structure to JSON.

Why use Object.defineProperty instead of Object.assign?

Object.assign always creates enumerable properties. Object.defineProperty gives explicit control — the fix sets enumerable: false while keeping the property writable and configurable, matching how native JavaScript Error handles cause.