Skip to content

Preserve AbortController reason in CanceledError and restore CommonJS entrypoint#7518

Open
sinteticx wants to merge 3 commits intoaxios:v1.xfrom
sinteticx:#7463
Open

Preserve AbortController reason in CanceledError and restore CommonJS entrypoint#7518
sinteticx wants to merge 3 commits intoaxios:v1.xfrom
sinteticx:#7463

Conversation

@sinteticx
Copy link
Copy Markdown

@sinteticx sinteticx commented Mar 16, 2026

Summary

Fixes a regression introduced in 1.13.6 where require('axios') in a CommonJS (Node.js) project would throw:
Cannot use import statement outside a module.
This was caused by package.json’s main field pointing to index.js (ESM), even though the intended CommonJS entrypoint exists at axios.cjs

Changes

  • ✅ Updated package.json to set "main": "./dist/node/axios.cjs" so CommonJS consumers correctly load the CJS build.
  • ✅ Added a new test file: commonjs-compatibility.cjs that:
  1. require()s axios
  2. Confirms it loads successfully
  3. Performs a basic axios.get() to validate runtime behavior

Why This Matters
This prevents downstream CommonJS-only projects (and nested deps) from breaking due to accidentally loading ESM source files via require().


Summary by cubic

Restores axios’s CommonJS entrypoint so require('axios') works in Node CJS, and preserves AbortController abort reasons in CanceledError for clearer cancellation errors across adapters and composed signals.

Description

  • Summary of changes
    • Set package.json main to ./dist/node/axios.cjs.
    • Pass signal.reason through HTTP/XHR adapters and composeSignals.
    • CanceledError now stores .reason and uses it as the message when none is provided.
  • Reasoning
    • Fixes a v1.13.6 regression where CJS require('axios') threw an ESM import error.
    • Retains abort reason to align with AbortController semantics and distinguish timeouts vs user cancels.
  • Additional context
    • No breaking changes: cancellations still throw AxiosError.ERR_CANCELED.
    • Works in Node and browsers.

Docs

  • CanceledError exposes .reason (string or Error). If AbortController.abort('foo') is used, the error message becomes 'foo'.
  • CommonJS usage is unchanged: require('axios') loads the CJS build.

Testing

  • Added test/unit/abort-reason.js to verify message and .reason for string/Error/no-reason cases and merged signals.
  • Added test/commonjs-compatibility.cjs to confirm require('axios') works and a basic GET runs.
  • No existing tests modified.

Written for commit eea799a. Summary will update on new commits.

CanceledError now stores the original abort reason (string or Error)
and adapters/composeSignals pass signal.reason. Added unit tests.

Closes axios#7434
Update   field to point at  (CommonJS build) instead of  (ESM).

Add  to validate  works in CommonJS environments without  errors.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 8 files

Confidence score: 3/5

  • There is a concrete regression risk in lib/helpers/composeSignals.js: abort listeners are added via wrapper callbacks but removed via onabort, so cleanup likely fails and can leave stale listeners attached.
  • Most other findings are test reliability gaps rather than direct runtime breakage (test/commonjs-compatibility.cjs not running in CI, and swallowing axios.get failures), which reduces confidence that compatibility regressions will be caught.
  • Given one high-confidence, user-impacting logic issue (7/10) plus incomplete test coverage around abort behavior in test/unit/abort-reason.js, this carries moderate merge risk and likely needs a fix or follow-up before relying on it.
  • Pay close attention to lib/helpers/composeSignals.js, test/commonjs-compatibility.cjs, and test/unit/abort-reason.js - listener cleanup appears incorrect and current tests may miss real compatibility/abort regressions.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/helpers/composeSignals.js">

<violation number="1" location="lib/helpers/composeSignals.js:52">
P1: Abort listeners are registered with wrapper callbacks but removed using `onabort`, so cleanup cannot detach them.</violation>
</file>

<file name="test/commonjs-compatibility.cjs">

<violation number="1" location="test/commonjs-compatibility.cjs:1">
P2: New CommonJS compatibility test file is not wired into any automated test command, so it will not run in CI.</violation>

<violation number="2" location="test/commonjs-compatibility.cjs:13">
P2: CommonJS compatibility script swallows `axios.get` failures, allowing success exit even when runtime request behavior is broken.</violation>
</file>

<file name="test/unit/abort-reason.js">

<violation number="1" location="test/unit/abort-reason.js:176">
P2: Tests are mislabeled as timeout/merged-signal coverage but only exercise manual `AbortController.abort(...)`, leaving real timeout/composed-signal logic untested.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


signals.forEach((signal) => signal.addEventListener('abort', onabort));
signals.forEach((signal) => {
signal.addEventListener('abort', () => onabort(signal.reason));
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Abort listeners are registered with wrapper callbacks but removed using onabort, so cleanup cannot detach them.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/helpers/composeSignals.js, line 52:

<comment>Abort listeners are registered with wrapper callbacks but removed using `onabort`, so cleanup cannot detach them.</comment>

<file context>
@@ -43,7 +48,9 @@ const composeSignals = (signals, timeout) => {
 
-    signals.forEach((signal) => signal.addEventListener('abort', onabort));
+    signals.forEach((signal) => {
+      signal.addEventListener('abort', () => onabort(signal.reason));
+    });
 
</file context>
Fix with Cubic

console.log('Status:', response.status);
})
.catch(error => {
console.log('GET request failed, but axios loaded:', error.message);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: CommonJS compatibility script swallows axios.get failures, allowing success exit even when runtime request behavior is broken.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/commonjs-compatibility.cjs, line 13:

<comment>CommonJS compatibility script swallows `axios.get` failures, allowing success exit even when runtime request behavior is broken.</comment>

<file context>
@@ -0,0 +1,14 @@
+    console.log('Status:', response.status);
+  })
+  .catch(error => {
+    console.log('GET request failed, but axios loaded:', error.message);
+  });
\ No newline at end of file
</file context>
Fix with Cubic

@@ -0,0 +1,14 @@
const axios = require('../');
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: New CommonJS compatibility test file is not wired into any automated test command, so it will not run in CI.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/commonjs-compatibility.cjs, line 1:

<comment>New CommonJS compatibility test file is not wired into any automated test command, so it will not run in CI.</comment>

<file context>
@@ -0,0 +1,14 @@
+const axios = require('../');
+
+console.log('Axios loaded successfully in CommonJS environment');
</file context>
Fix with Cubic

@@ -0,0 +1,214 @@
'use strict';
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Tests are mislabeled as timeout/merged-signal coverage but only exercise manual AbortController.abort(...), leaving real timeout/composed-signal logic untested.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/unit/abort-reason.js, line 176:

<comment>Tests are mislabeled as timeout/merged-signal coverage but only exercise manual `AbortController.abort(...)`, leaving real timeout/composed-signal logic untested.</comment>

<file context>
@@ -0,0 +1,214 @@
+    }
+  });
+
+  it('should work with merged signals from two controllers', async () => {
+    const timeoutController = new AbortController();
+    const userController = new AbortController();
</file context>
Fix with Cubic

@sinteticx
Copy link
Copy Markdown
Author

Hi @davecardwell, Could you review my changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant