Skip to content

fix(fetch): normalize Safari timeout errors to AxiosError#7191

Closed
Mostafa-Khairy0 wants to merge 7 commits intoaxios:v1.xfrom
Mostafa-Khairy0:fix-fetch-timeout-safari
Closed

fix(fetch): normalize Safari timeout errors to AxiosError#7191
Mostafa-Khairy0 wants to merge 7 commits intoaxios:v1.xfrom
Mostafa-Khairy0:fix-fetch-timeout-safari

Conversation

@Mostafa-Khairy0
Copy link
Copy Markdown
Contributor

@Mostafa-Khairy0 Mostafa-Khairy0 commented Oct 28, 2025

🐞 Bug Fix: Safari TypeError on Fetch Timeout

Axios should throw a proper AxiosError when a request times out, but in Safari the fetch adapter throws a restricted DOM error:

TypeError: The DOMException.message getter can only be used on instances of DOMException

This causes Axios to incorrectly classify the timeout as a generic network failure instead of a timeout error.


✅ Expected Behavior

{
  "message": "timeout 2000 of ms exceeded",
  "code": "ETIMEDOUT",
  "name": "AxiosError"
}

❌ Actual Safari Behavior

TypeError: The DOMException.message getter can only be used on instances of DOMException

🔧 What This PR Fixes

This PR ensures Safari timeout errors thrown by the fetch adapter are converted into proper Axios timeout errors by using:

✔️ Correct error type (AxiosError)
✔️ Proper timeout code (ETIMEDOUT)
✔️ Preserves original error cause
✔️ Full cross-browser consistency
axios.get("https://fakeresponder.com/?sleep=50000", {
  adapter: "fetch",
  timeout: 2000,
});

Environment:
• Safari: 26.0.1
• macOS: 15.6.1
• Axios: 1.13.0
• Adapter: fetch

🔗 Related Issue

Fixes Axios fetch adapter throws TypeError on timeout in Safari #7190

@nidhishgajjar

This comment was marked as spam.

1 similar comment
@nidhishgajjar

This comment was marked as spam.

@jasonsaayman jasonsaayman self-requested a review as a code owner April 26, 2026 10:39
The previous catch fired on any DOMException or AbortError, which
reclassified user-initiated cancellations as ETIMEDOUT. Switch the
discriminator to composedSignal.reason being the AxiosError(ETIMEDOUT)
injected by composeSignals so only true timeouts take the timeout path.
Drop the bare DOMException reference (would ReferenceError outside
browsers/modern Node), preserve cause for debugging, and align the
message text with composeSignals and the http adapter.

Add tests covering: real timeout produces AxiosError(ETIMEDOUT);
manual abort is not misclassified (regression); Safari simulation
where fetch rejects with a generic DOMException still surfaces
ETIMEDOUT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jasonsaayman jasonsaayman added priority::medium A medium priority commit::fix The PR is related to a bugfix labels Apr 26, 2026
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.

No issues found across 2 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

@zozo123
Copy link
Copy Markdown
Contributor

zozo123 commented Apr 27, 2026

I tried to push a follow-up commit here to keep this PR as the landing path, but GitHub denied write access to Mostafa-Khairy0:fix-fetch-timeout-safari from my account.

I opened #10806 as a fallback based on this PR’s work, with commit co-author credit for @Mostafa-Khairy0 and @jasonsaayman. It keeps the composedSignal.reason approach, preserves cancellation as cancellation, updates the Bun smoke expectation to ETIMEDOUT, and links Fixes #7190.

If maintainers prefer this PR to remain the landing PR, the patch from #10806 can be applied here directly.

zozo123 added a commit to zozo123/axios that referenced this pull request Apr 27, 2026
Based on the work in axios#7191.

Co-authored-by: Mostafa-Khairy0 <mostafakhairy0305@gmail.com>
Co-authored-by: Jason Saayman <jasonsaayman@gmail.com>
@jasonsaayman
Copy link
Copy Markdown
Member

Hey @Mostafa-Khairy0, thanks again for digging into this. The Safari abort behaviour is genuinely weird and you nailed the underlying cause: the fix has to come from the composed signal's reason, not from err.

Quick context on what's happening with this PR. After the review thread we wanted to extend the same approach to also cover user cancellations (so CanceledError keeps its ERR_CANCELED identity instead of being misclassified) and reuse the existing reason instance rather than constructing a new AxiosError. GitHub wouldn't let us push directly to your branch, so a follow-up was opened at #10806 that builds on your work and credits you in the description.

A couple of small things in #10806 worth calling out, in case you're curious:

  • The Safari reproduction test now uses an object with name/message getters that throw, which is the actual off-prototype bug. A real DOMException doesn't reproduce it, so the original test in this PR was passing via the existing TypeError text-match branch rather than the new code path.
  • The branch handles any AxiosError-shaped reason on the composed signal, so user cancels propagate as CanceledError (ERR_CANCELED) and timeouts as AxiosError (ETIMEDOUT).
  • Bun smoke test got updated to match the new ETIMEDOUT expectation.

Closing this in favour of #10806. The credit there points back here, and the original framing of the bug is what unblocked the fix — appreciate you sticking with it.

jasonsaayman added a commit that referenced this pull request Apr 27, 2026
Based on the work in #7191.

Co-authored-by: Mostafa-Khairy0 <mostafakhairy0305@gmail.com>
Co-authored-by: Jason Saayman <jasonsaayman@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commit::fix The PR is related to a bugfix priority::medium A medium priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants