diff --git a/src/infra/unhandled-rejections.test.ts b/src/infra/unhandled-rejections.test.ts index 1ec144ba1..696ee1310 100644 --- a/src/infra/unhandled-rejections.test.ts +++ b/src/infra/unhandled-rejections.test.ts @@ -87,6 +87,18 @@ describe("isTransientNetworkError", () => { expect(isTransientNetworkError(error)).toBe(true); }); + it("returns true for fetch failed with unknown cause (Regression Test for #3974)", () => { + // Simulate Cause with NO code + const causeNoCode = new Error("Some obscure network error"); + const errorNoCode = Object.assign(new TypeError("fetch failed"), { cause: causeNoCode }); + expect(isTransientNetworkError(errorNoCode)).toBe(true); + + // Simulate Cause with UNKNOWN code + const causeUnknown = Object.assign(new Error("Unknown"), { code: "UNKNOWN_123" }); + const errorUnknown = Object.assign(new TypeError("fetch failed"), { cause: causeUnknown }); + expect(isTransientNetworkError(errorUnknown)).toBe(true); + }); + it("returns true for nested cause chain with network error", () => { const innerCause = Object.assign(new Error("connection reset"), { code: "ECONNRESET" }); const outerCause = Object.assign(new Error("wrapper"), { cause: innerCause }); diff --git a/src/infra/unhandled-rejections.ts b/src/infra/unhandled-rejections.ts index 4d2a48d23..029e37366 100644 --- a/src/infra/unhandled-rejections.ts +++ b/src/infra/unhandled-rejections.ts @@ -83,8 +83,14 @@ export function isTransientNetworkError(err: unknown): boolean { // "fetch failed" TypeError from undici (Node's native fetch) if (err instanceof TypeError && err.message === "fetch failed") { + // Almost all cases of "fetch failed" are network related (DNS, timeout, connection refuse). + // If we can't determine otherwise, assume transient to prevent crashing the gateway. const cause = getErrorCause(err); - if (cause) return isTransientNetworkError(cause); + if (cause) { + if (isTransientNetworkError(cause)) return true; + // If cause exists but isn't explicitly fatal, treat "fetch failed" as transient + if (!isFatalError(cause) && !isConfigError(cause)) return true; + } return true; }