fix(gateway): treat all fetch failed TypeErrors as transient
When a TypeError("fetch failed") from undici had a cause with an
unrecognized error code, isTransientNetworkError() would recurse into
the cause, fail to match, and return false — causing the gateway to
exit via process.exit(1).
Since the outer TypeError("fetch failed") already indicates a
network-level failure, treat it as transient unconditionally regardless
of the cause chain contents.
Added tests for fetch failures with unrecognized cause and no cause.
Fixes #4425
This commit is contained in:
parent
9025da2296
commit
4f3b78aa5e
@ -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",
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user