Skip to content

Commit 3c8ad8c

Browse files
committed
fix(browser): default non-finite fetch timeouts
1 parent b2bdad5 commit 3c8ad8c

2 files changed

Lines changed: 25 additions & 2 deletions

File tree

extensions/browser/src/browser/client-fetch.loopback-auth.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,23 @@ describe("fetchBrowserJson loopback auth", () => {
543543
);
544544
});
545545

546+
it("uses the default timeout for non-finite absolute HTTP timeout failures", async () => {
547+
vi.stubGlobal(
548+
"fetch",
549+
vi.fn(async () => {
550+
throw new Error("timed out");
551+
}),
552+
);
553+
554+
await expectThrownBrowserFetchError(
555+
() => fetchBrowserJson<{ ok: boolean }>("http://example.com/", { timeoutMs: Number.NaN }),
556+
{
557+
contains: ["timed out after 5000ms"],
558+
omits: ["NaNms", "Do NOT retry the browser tool"],
559+
},
560+
);
561+
});
562+
546563
it("omits no-retry hint for absolute HTTP abort failures", async () => {
547564
vi.stubGlobal(
548565
"fetch",

extensions/browser/src/browser/client-fetch.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { parseFiniteNumber } from "openclaw/plugin-sdk/number-runtime";
12
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
23
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
34
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -162,6 +163,11 @@ function appendBrowserToolModelHint(message: string): string {
162163

163164
type BrowserFetchFailureKind = "timeout" | "aborted" | "persistent";
164165

166+
function resolveBrowserFetchTimeoutMs(timeoutMs: number | undefined): number {
167+
const parsed = parseFiniteNumber(timeoutMs);
168+
return Math.max(1, Math.floor(parsed ?? 5000));
169+
}
170+
165171
function classifyBrowserFetchFailure(err: unknown): BrowserFetchFailureKind {
166172
const msg = normalizeErrorMessage(err);
167173
const msgLower = normalizeLowercaseStringOrEmpty(msg);
@@ -228,7 +234,7 @@ async function fetchHttpJson<T>(
228234
url: string,
229235
init: RequestInit & { timeoutMs?: number },
230236
): Promise<T> {
231-
const timeoutMs = init.timeoutMs ?? 5000;
237+
const timeoutMs = resolveBrowserFetchTimeoutMs(init.timeoutMs);
232238
const ctrl = new AbortController();
233239
const upstreamSignal = init.signal;
234240
let upstreamAbortListener: (() => void) | undefined;
@@ -278,7 +284,7 @@ export async function fetchBrowserJson<T>(
278284
url: string,
279285
init?: RequestInit & { timeoutMs?: number },
280286
): Promise<T> {
281-
const timeoutMs = init?.timeoutMs ?? 5000;
287+
const timeoutMs = resolveBrowserFetchTimeoutMs(init?.timeoutMs);
282288
let isDispatcherPath = false;
283289
try {
284290
if (isAbsoluteHttp(url)) {

0 commit comments

Comments
 (0)