fix: move cause-chain traversal before timeout heuristic (review feedback)

This commit is contained in:
Catalin Lupuleti 2026-03-09 22:39:49 +00:00 committed by Darshil
parent dac220bd88
commit c1c74f9952
2 changed files with 15 additions and 4 deletions

View File

@ -238,10 +238,9 @@ export function resolveFailoverReasonFromError(err: unknown): FailoverReason | n
) { ) {
return "timeout"; return "timeout";
} }
if (isTimeoutError(err)) { // Walk into error cause chain *before* timeout heuristics so that a specific
return "timeout"; // cause (e.g. RESOURCE_EXHAUSTED wrapped in AbortError) overrides a parent
} // message-based "timeout" guess from isTimeoutError.
// Walk into error cause chain (e.g. AbortError wrapping a rate-limit cause)
const cause = getErrorCause(err); const cause = getErrorCause(err);
if (cause && cause !== err) { if (cause && cause !== err) {
const causeReason = resolveFailoverReasonFromError(cause); const causeReason = resolveFailoverReasonFromError(cause);
@ -249,6 +248,9 @@ export function resolveFailoverReasonFromError(err: unknown): FailoverReason | n
return causeReason; return causeReason;
} }
} }
if (isTimeoutError(err)) {
return "timeout";
}
if (!message) { if (!message) {
return null; return null;
} }

View File

@ -394,6 +394,15 @@ describe("runWithModelFallback probe logic", () => {
// All three candidates must be attempted — the abort must not short-circuit // All three candidates must be attempted — the abort must not short-circuit
expect(run).toHaveBeenCalledTimes(3); expect(run).toHaveBeenCalledTimes(3);
// Verify the primary error is classified as rate_limit, not timeout — the
// cause chain (RESOURCE_EXHAUSTED) must override the parent AbortError message.
try {
await runWithModelFallback({ cfg, provider: "google", model: "gemini-3-flash-preview", run });
} catch (err) {
expect(String(err)).toContain("(rate_limit)");
expect(String(err)).not.toMatch(/gemini.*\(timeout\)/);
}
expect(run).toHaveBeenNthCalledWith(1, "google", "gemini-3-flash-preview", { expect(run).toHaveBeenNthCalledWith(1, "google", "gemini-3-flash-preview", {
allowTransientCooldownProbe: true, allowTransientCooldownProbe: true,
}); });