Skip to content

[Detail Bug] SDK EventQueue: timeout/network DOMException errors are not retried #167

@detail-app

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_ea7bf3e3-a2f4-4402-9351-baa0e1eaa1f5/bugs/bug_f7e028ee-71b7-45e7-91d6-f1993997f352

Summary

  • Context: isNetworkError is a validator used by EventQueue to determine if a failed network request should be retried.
  • Bug: The function only recognizes TypeError objects and misses network errors reported as DOMException, such as TimeoutError and NetworkError.
  • Actual vs. expected: It returns false for TimeoutError and NetworkError DOMException objects, whereas it should return true as these represent retryable network failures.
  • Impact: Transient timeouts and other DOMException-based network errors are not retried, leading to lost analytics events and decreased reliability of the SDK.

Code with Bug

00003| const isError = (value: any) => objectToString.call(value) === "[object Error]"; // <-- BUG 🔴 [Does not match [object DOMException]]
...
00016| export function isNetworkError(error: any) {
00017|   const isValid =
00018|     error &&
00019|     isError(error) &&
00020|     error.name === "TypeError" && // <-- BUG 🔴 [Restricts to TypeError, missing TimeoutError and NetworkError]
00021|     typeof error.message === "string";

Explanation

  • Modern fetch implementations can throw timeout/network failures as DOMException (e.g., AbortSignal.timeout() throws a TimeoutError DOMException).
  • The helper isError rejects DOMException because Object.prototype.toString returns "[object DOMException]", so the validator short-circuits and returns false.
  • Even if the isError check is relaxed, isNetworkError currently requires error.name === "TypeError", so retryable DOMException names like TimeoutError and NetworkError still won’t be treated as network errors.

Recommended Fix

  • Accept both [object Error] and [object DOMException] in isError.
  • Treat TimeoutError and NetworkError as retryable by expanding the name allowlist.
  • Add the common timeout message to the errorMessages set.
const isError = (value: any) => {
  const tag = objectToString.call(value);
  return tag === "[object Error]" || tag === "[object DOMException]"; // <-- FIX 🟢
};

// Add to errorMessages
"The operation was aborted due to timeout", // Node 18+, Chrome
  // Update isNetworkError
  error &&
    isError(error) &&
    (error.name === "TypeError" ||
      error.name === "TimeoutError" ||
      error.name === "NetworkError"); // <-- FIX 🟢

History

This bug was introduced in commit a774609. This commit replaced the external is-network-error dependency with a custom internal implementation as part of a major SDK refactor. The new implementation introduced overly restrictive checks that strictly required the error to be a TypeError and match [object Error], failing to account for DOMException types used by modern fetch implementations for timeouts and network failures.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions