claude-retry
v0.1.0
Published
Smart retry for Anthropic API calls. Respects retry-after headers, exponential backoff with jitter, customizable policies. Zero dependencies.
Maintainers
Readme
claude-retry
Smart retry for Anthropic API calls. Respects
retry-afterheaders, exponential backoff with jitter, customizable policies. Zero dependencies.
import Anthropic from "@anthropic-ai/sdk";
import { withRetry } from "claude-retry";
const client = new Anthropic();
const reply = await withRetry(
() => client.messages.create({
model: "claude-opus-4-7",
max_tokens: 1024,
messages: [{ role: "user", content: "hi" }],
}),
{
maxRetries: 5,
onRetry: ({ error, attempt, delay }) =>
console.warn(`retry ${attempt} after ${delay}ms`, error),
}
);Why this exists
The Anthropic SDK has built-in retry, but in production you often want more:
- Respect
retry-after— When Anthropic returns 429 with aretry-afterheader, the SDK doesn't always honor the suggested wait. This package does. - Observability — Hook into every retry attempt for logging/metrics. The SDK is silent.
- Custom policies — Decide per-call whether a given error should retry (e.g., never retry tool-validation errors).
- AbortSignal support — Cancel a retry loop mid-flight when the user navigates away.
- Works on anything — Not just
client.messages.create. Wrap any async function: tool execution, streaming setup, custom HTTP calls.
Install
npm install claude-retry
# or
pnpm add claude-retryAPI
withRetry(fn, options?)
function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;Retries fn until it succeeds, fails non-retryably, or exhausts the budget.
RetryOptions
| Option | Default | Description |
|---|---|---|
| maxRetries | 3 | Max retry attempts (in addition to the initial call) |
| baseDelay | 1000 | Initial delay in ms before first retry |
| maxDelay | 30000 | Maximum delay cap in ms |
| jitter | true | Add ±25% randomization to delays (thundering herd protection) |
| respectRetryAfter | true | Honor retry-after header when present |
| shouldRetry | isAnthropicRetryable | Predicate to decide if an error is retryable |
| onRetry | — | Callback before each retry: ({ error, attempt, delay }) => ... |
| signal | — | AbortSignal to cancel the loop |
isAnthropicRetryable(err)
Default retry predicate tuned for Anthropic:
- 429 (rate limit) → ✅ retry
- 5xx (server errors) → ✅ retry
- Network codes (
ETIMEDOUT,ECONNRESET,ECONNREFUSED, ...) → ✅ retry - Network-pattern messages (
/network|timeout|fetch failed|socket hang up/) → ✅ retry - 4xx other than 429 → ❌ don't retry
- Anything else → ❌ don't retry
parseRetryAfter(err)
Extracts the retry-after header (supports both seconds and HTTP-date), returns delay in ms or null.
computeBackoff({ attempt, baseDelay, maxDelay, jitter })
Exposed so you can compute backoff yourself if you're building higher-level abstractions.
Recipes
Always wait at least 2 seconds between retries
await withRetry(fn, { baseDelay: 2000, jitter: false });Stricter policy — only retry 429, not 5xx
import { withRetry } from "claude-retry";
await withRetry(fn, {
shouldRetry: (err) => (err as { status?: number })?.status === 429,
});With AbortSignal (cancel on tab close)
const controller = new AbortController();
window.addEventListener("beforeunload", () => controller.abort());
await withRetry(fn, { signal: controller.signal });With observability
import { withRetry } from "claude-retry";
import { metrics } from "./your-metrics";
await withRetry(fn, {
onRetry: ({ error, attempt, delay }) => {
const status = (error as { status?: number }).status;
metrics.increment("anthropic.retry", { status, attempt });
metrics.histogram("anthropic.retry.delay_ms", delay);
},
});Wrap streaming
withRetry retries the setup call, not the stream itself. If a stream fails mid-way, it gets re-issued from scratch — be aware that you'll get a new full response.
const stream = await withRetry(() => client.messages.stream({...}));Tests & build
npm install
npm test # vitest run
npm run build # tscLicense
MIT — © 2026 xiangnuans
Part of a series
This is the 2nd package in a series of small, focused Claude SDK helpers built in public.
- ✅
claude-stream-collector— Consume streaming events into typed result - ✅
claude-retry— Smart retry with backoff (this package) - 🚧
claude-cost-tracker— Real-time token / dollar tracking (next) - 🚧
claude-prompt-toolkit— Prompt templating, versioning
Follow the journey: ship-log-2026.
