diff --git a/src/infra/unhandled-rejections.fatal-detection.test.ts b/src/infra/unhandled-rejections.fatal-detection.test.ts index e991c67c9..2ebd70166 100644 --- a/src/infra/unhandled-rejections.fatal-detection.test.ts +++ b/src/infra/unhandled-rejections.fatal-detection.test.ts @@ -114,6 +114,32 @@ describe("installUnhandledRejectionHandler - fatal detection", () => { ); }); + it("does NOT exit on undici fetch failures with unrecognized cause", () => { + const fetchErr = Object.assign(new TypeError("fetch failed"), { + cause: new Error("some unknown undici error"), + }); + + process.emit("unhandledRejection", fetchErr, Promise.resolve()); + + expect(exitCalls).toEqual([]); + expect(consoleWarnSpy).toHaveBeenCalledWith( + "[openclaw] Non-fatal unhandled rejection (continuing):", + expect.stringContaining("fetch failed"), + ); + }); + + it("does NOT exit on undici fetch failures without cause", () => { + const fetchErr = new TypeError("fetch failed"); + + process.emit("unhandledRejection", fetchErr, Promise.resolve()); + + expect(exitCalls).toEqual([]); + expect(consoleWarnSpy).toHaveBeenCalledWith( + "[openclaw] Non-fatal unhandled rejection (continuing):", + expect.stringContaining("fetch failed"), + ); + }); + it("does NOT exit on DNS resolution failures", () => { const dnsErr = Object.assign(new Error("DNS resolve failed"), { code: "UND_ERR_DNS_RESOLVE_FAILED", diff --git a/src/infra/unhandled-rejections.ts b/src/infra/unhandled-rejections.ts index 4d2a48d23..45a92ba3a 100644 --- a/src/infra/unhandled-rejections.ts +++ b/src/infra/unhandled-rejections.ts @@ -81,10 +81,10 @@ export function isTransientNetworkError(err: unknown): boolean { const code = extractErrorCodeWithCause(err); if (code && TRANSIENT_NETWORK_CODES.has(code)) return true; - // "fetch failed" TypeError from undici (Node's native fetch) + // "fetch failed" TypeError from undici (Node's native fetch). + // Always treat as transient — the cause may carry an unknown code + // but the outer TypeError already indicates a network-level failure. if (err instanceof TypeError && err.message === "fetch failed") { - const cause = getErrorCause(err); - if (cause) return isTransientNetworkError(cause); return true; }