Problem
Both GeminiClient and AnthropicClient classify retryable errors by scanning the error message string:
// src/model/gemini.ts:63
const isRetryable =
msg.includes("429") || msg.includes("500") || msg.includes("RESOURCE_EXHAUSTED");
// src/model/anthropic.ts:69
const isRetryable =
msg.includes("429") || msg.includes("500") || msg.includes("overloaded");
Error message text is not a stable API — SDK version bumps, proxy wrappers, or locale changes can alter the message format. A retry that should fire won't, and hard errors may be silently retried.
Expected behavior
Retry classification should use the structured status field on the SDK error object, which is stable across SDK versions.
Fix
Use the SDK's typed error classes:
// Anthropic
import Anthropic from "@anthropic-ai/sdk";
const isRetryable =
err instanceof Anthropic.APIError && [429, 500, 502, 529].includes(err.status);
// Gemini — GoogleGenerativeAIError exposes .status
import { GoogleGenerativeAIError } from "@google/generative-ai";
const isRetryable =
err instanceof GoogleGenerativeAIError && [429, 500, 502, 503].includes(err.status ?? 0);
Location
src/model/gemini.ts:63
src/model/anthropic.ts:69
Problem
Both
GeminiClientandAnthropicClientclassify retryable errors by scanning the error message string:Error message text is not a stable API — SDK version bumps, proxy wrappers, or locale changes can alter the message format. A retry that should fire won't, and hard errors may be silently retried.
Expected behavior
Retry classification should use the structured
statusfield on the SDK error object, which is stable across SDK versions.Fix
Use the SDK's typed error classes:
Location
src/model/gemini.ts:63src/model/anthropic.ts:69