diff --git a/src/infra/provider-usage.fetch.shared.test.ts b/src/infra/provider-usage.fetch.shared.test.ts index 72195d9f01f..0fea8ee4b11 100644 --- a/src/infra/provider-usage.fetch.shared.test.ts +++ b/src/infra/provider-usage.fetch.shared.test.ts @@ -7,6 +7,8 @@ import { parseFiniteNumber, } from "./provider-usage.fetch.shared.js"; +const MAX_TIMER_TIMEOUT_MS = 2_147_000_000; + function requireFetchCall( mock: ReturnType, ): [URL | RequestInfo, RequestInit | undefined] { @@ -92,6 +94,19 @@ describe("provider usage fetch shared helpers", () => { } }); + it("caps oversized request timeouts before scheduling", async () => { + const timeoutSpy = vi + .spyOn(globalThis, "setTimeout") + .mockReturnValue(1 as unknown as ReturnType); + vi.spyOn(globalThis, "clearTimeout").mockImplementation(() => undefined); + const fetchFnMock = vi.fn(async () => new Response("{}", { status: 200 })); + const fetchFn = withFetchPreconnect(fetchFnMock); + + await fetchJson("https://example.com/usage", {}, MAX_TIMER_TIMEOUT_MS + 1_000_000, fetchFn); + + expect(timeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS); + }); + it("maps configured status codes to token expired", () => { const snapshot = buildUsageHttpErrorSnapshot({ provider: "openai-codex", diff --git a/src/infra/provider-usage.fetch.shared.ts b/src/infra/provider-usage.fetch.shared.ts index 2fc7826d8c1..84e1d877e01 100644 --- a/src/infra/provider-usage.fetch.shared.ts +++ b/src/infra/provider-usage.fetch.shared.ts @@ -1,3 +1,4 @@ +import { resolveTimerTimeoutMs } from "../shared/number-coercion.js"; import { parseFiniteNumber as parseFiniteNumberish } from "./parse-finite-number.js"; import { PROVIDER_LABELS } from "./provider-usage.shared.js"; import type { ProviderUsageSnapshot, UsageProviderId } from "./provider-usage.types.js"; @@ -8,8 +9,9 @@ export async function fetchJson( timeoutMs: number, fetchFn: typeof fetch, ): Promise { + const safeTimeoutMs = resolveTimerTimeoutMs(timeoutMs, 1); const controller = new AbortController(); - const timer = setTimeout(controller.abort.bind(controller), timeoutMs); + const timer = setTimeout(controller.abort.bind(controller), safeTimeoutMs); try { return await fetchFn(url, { ...init, signal: controller.signal }); } finally {