← Back to PRs

#11874: fix: handle fetch rejections in provider usage withTimeout

by Zjianru open 2026-02-08 13:22 View on GitHub →
stale
Fixes #11786. ## Problem `withTimeout()` in `provider-usage.shared.ts` races the work promise against a timeout, but only the timeout branch resolves to the fallback value. When the work promise **rejects** (e.g. `TypeError: fetch failed` from network errors), the rejection propagates through `Promise.race`, bypassing the fallback entirely. This causes `loadProviderUsageSummary` to throw when any provider fetch encounters a network error. Callers catch the exception but lose all provider data, resulting in stale or default UI display (e.g. "100% left" for Gemini quota even when significant quota has been consumed). **Error from reporter's logs:** ``` warn [openclaw] Non-fatal unhandled rejection (continuing): TypeError: fetch failed Target URL: https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota ``` ## Root Cause ```typescript // Before: rejection propagates through Promise.race return await Promise.race([ work, // ← if this rejects, race rejects too new Promise<T>((resolve) => { timeout = setTimeout(() => resolve(fallback), ms); }), ]); ``` ## Fix ```typescript // After: catch rejection and resolve to fallback return await Promise.race([ work.catch(() => fallback), // ← network errors now resolve to fallback new Promise<T>((resolve) => { timeout = setTimeout(() => resolve(fallback), ms); }), ]); ``` One-character diff in production code. The `.catch(() => fallback)` ensures network errors, DNS failures, and other fetch exceptions are handled identically to timeouts — graceful fallback across **all** provider usage fetchers (Gemini, Claude, Copilot, Antigravity, Codex, MiniMax, Zai). ## Tests New test file `provider-usage.shared.test.ts` with 5 cases: - Normal resolution (work completes before timeout) - Timeout fallback (work is slow) - **Rejection fallback with TypeError** (the reported bug) - Rejection fallback with generic Error - Rejection fallback with object fallback value ``` ✓ src/infra/provider-usage.shared.test.ts (5 tests) ✓ src/infra/provider-usage.test.ts (10 tests) ✓ src/infra/provider-usage.fetch.antigravity.test.ts (18 tests) ``` All 33 existing + new tests pass. <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR updates `withTimeout()` in `src/infra/provider-usage.shared.ts` so that rejections from the raced work promise (e.g. `TypeError: fetch failed`) resolve to the provided fallback instead of propagating through `Promise.race`. This prevents `loadProviderUsageSummary` and other provider-usage fetchers from failing hard on transient network errors and aligns rejection behavior with the existing timeout fallback behavior. It also adds a focused unit test suite in `src/infra/provider-usage.shared.test.ts` covering success-before-timeout, timeout fallback, and several rejection→fallback scenarios (including non-`Error` fallbacks). <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk. - Change is narrowly scoped to `withTimeout()` by converting work rejections into fallback resolution, which matches the function’s existing fallback-on-timeout contract. Added tests explicitly cover success, timeout, and multiple rejection variants. No behavioral regressions were found in call sites based on the explored code paths. - No files require special attention <!-- greptile_other_comments_section --> <sub>(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!</sub> <!-- /greptile_comment -->

Most Similar PRs