#11131·axios

Axios.request drops reconstructed caller stacks with fewer than three frames

Author: LineGuCreated Aug 7, 2026Updated Aug 7, 2026

Describe the issue

Axios.request decorates rejected native errors by capturing a second stack in its catch block and appending that stack to the original error. This restores the request call site for errors created asynchronously inside an adapter.

Since Axios 1.15.0, a reconstructed caller stack containing only one or two frames is not appended.

The behavior appears to be an unrelated side effect of commit 3631854, which replaced the previous regular-expression-based stack trimming with indexOf calls.

When the reconstructed stack has fewer than three frames, there is no second newline:

const stackWithoutTwoTopLines =
  secondNewlineIndex === -1 ? '' : stack.slice(secondNewlineIndex + 1);

This makes the duplicate check equivalent to:

err.stack.endsWith('')

which is always true, so the reconstructed caller stack is skipped.

This is particularly visible for XHR errors created inside onloadend / settle. Their original stack describes the adapter callback, while a shallow V8 async chain may reconstruct only these two caller frames:

at Axios.request
at async requestFromNamedCaller

The regression is present in Axios 1.15.0 through 1.19.0.

Example code

Save as reproduction.mjs and run with Node:

import axios, { AxiosError } from 'axios';

const originalStackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 2;

async function requestFromNamedCaller() {
  await axios.request({
    url: 'http://localhost/test',
    adapter: () =>
      new Promise((_, reject) => {
        setTimeout(() => reject(new AxiosError('adapter failure')), 0);
      }),
  });
}

try {
  await requestFromNamedCaller();
} catch (error) {
  console.log(error.stack);
  console.log(
    'caller preserved:',
    error.stack.includes('requestFromNamedCaller')
  );
} finally {
  Error.stackTraceLimit = originalStackTraceLimit;
}

Expected behavior

The reconstructed request stack should be appended, and the output should contain:

at Axios.request
at async requestFromNamedCaller
caller preserved: true

This matches the behavior before Axios 1.15.0.

Actual behavior

With Axios 1.19.0, the reconstructed stack is skipped:

caller preserved: false

The behavior differs depending on the reconstructed stack depth:

Reconstructed caller stack Axios ≤1.14 duplicate suffix Axios ≥1.15 duplicate suffix
1–2 frames Entire reconstructed stack Empty string
3+ frames Stack after the first two frames Stack after the first two frames

A possible fix is to preserve the pre-1.15 fallback:

const stackWithoutTwoTopLines =
- secondNewlineIndex === -1 ? '' : stack.slice(secondNewlineIndex + 1);
+ secondNewlineIndex === -1 ? stack : stack.slice(secondNewlineIndex + 1);

The existing duplicate detection for stacks containing three or more frames remains unchanged.

Environment

  • Axios version: 1.19.0
  • Adapter: Custom asynchronous adapter for the minimal reproduction; originally observed with XHR
  • Runtime: Node.js 24.17.0; also observed in V8/Chrome
  • OS: macOS for the minimal reproduction
  • Additional context: The issue is most visible with shallow async call chains whose reconstructed caller stack contains only two frames.

:surfer: