From cdab5fc16ab52fbf8cc15bd6a40d9cfe7fe7fc8b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 30 May 2026 17:19:48 -0400 Subject: [PATCH] fix(infra): clamp provider usage timeout --- src/infra/provider-usage.shared.test.ts | 13 +++++++++++++ src/infra/provider-usage.shared.ts | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/infra/provider-usage.shared.test.ts b/src/infra/provider-usage.shared.test.ts index f0b75867415..d815eabe64d 100644 --- a/src/infra/provider-usage.shared.test.ts +++ b/src/infra/provider-usage.shared.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, it, vi } from "vitest"; +import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js"; import { clampPercent, resolveUsageProviderId, withTimeout } from "./provider-usage.shared.js"; describe("provider-usage.shared", () => { @@ -65,6 +66,18 @@ describe("provider-usage.shared", () => { await expect(result).resolves.toBe("fallback"); }); + it("clamps oversized timeout delays before scheduling", async () => { + vi.useFakeTimers(); + const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); + + const result = withTimeout(new Promise(() => {}), Number.MAX_SAFE_INTEGER, "fallback"); + + expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS); + + await vi.advanceTimersByTimeAsync(MAX_TIMER_TIMEOUT_MS); + await expect(result).resolves.toBe("fallback"); + }); + it("clears the timeout after successful work", async () => { const clearTimeoutSpy = vi.spyOn(globalThis, "clearTimeout"); diff --git a/src/infra/provider-usage.shared.ts b/src/infra/provider-usage.shared.ts index c4c04f80049..5e211ae160e 100644 --- a/src/infra/provider-usage.shared.ts +++ b/src/infra/provider-usage.shared.ts @@ -1,4 +1,5 @@ import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id"; +import { resolveTimerTimeoutMs } from "../shared/number-coercion.js"; import type { UsageProviderId } from "./provider-usage.types.js"; export const DEFAULT_TIMEOUT_MS = 5000; @@ -71,11 +72,12 @@ export const clampPercent = (value: number) => export const withTimeout = async (work: Promise, ms: number, fallback: T): Promise => { let timeout: NodeJS.Timeout | undefined; + const timeoutMs = resolveTimerTimeoutMs(ms, 1); try { return await Promise.race([ work, new Promise((resolve) => { - timeout = setTimeout(() => resolve(fallback), ms); + timeout = setTimeout(() => resolve(fallback), timeoutMs); }), ]); } finally {