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", () => {
|
it("does NOT exit on DNS resolution failures", () => {
|
||||||
const dnsErr = Object.assign(new Error("DNS resolve failed"), {
|
const dnsErr = Object.assign(new Error("DNS resolve failed"), {
|
||||||
code: "UND_ERR_DNS_RESOLVE_FAILED",
|
code: "UND_ERR_DNS_RESOLVE_FAILED",
|
||||||
|
|||||||
@ -81,10 +81,10 @@ export function isTransientNetworkError(err: unknown): boolean {
|
|||||||
const code = extractErrorCodeWithCause(err);
|
const code = extractErrorCodeWithCause(err);
|
||||||
if (code && TRANSIENT_NETWORK_CODES.has(code)) return true;
|
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") {
|
if (err instanceof TypeError && err.message === "fetch failed") {
|
||||||
const cause = getErrorCause(err);
|
|
||||||
if (cause) return isTransientNetworkError(cause);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user