Python 88.3%
TypeScript 7.6%
Shell 4.1%
1#!/usr/bin/env python32"""Robust Anthropic error handling (Python SDK): typed exceptions, retry matrix, request ids, streaming errors.34STATUS: LIVE_VERIFIED 2026-09-18 (deliberate 404 + 400 on claude-haiku-4-5-20251001; nothing billed).5Run: .venv/bin/python examples/shared/errors/anthropic_error_handling.py6"""7import random8import sys9import time10from pathlib import Path1112sys.path.insert(0, str(Path(__file__).resolve().parents[3]))13from scripts import live # noqa: E40214import anthropic # noqa: E4021516# max_retries=0 so the demo shows the raw first failure; production: keep the default 2 (SDK retries 408/409/429/5xx + connection errors, honors retry-after).17client = anthropic.Anthropic(max_retries=0, timeout=60)18MODEL = "claude-haiku-4-5-20251001"1920RETRYABLE = (anthropic.RateLimitError, anthropic.InternalServerError, anthropic.APIConnectionError, anthropic.APITimeoutError) # 429, >=500 (incl. 529 OverloadedError), network21FATAL = (anthropic.BadRequestError, anthropic.AuthenticationError, anthropic.PermissionDeniedError, anthropic.NotFoundError, anthropic.UnprocessableEntityError) # 400/401/403/404/422 — fix the request222324def call_with_backoff(fn, attempts=4, base=0.5, cap=8.0):25 for i in range(attempts):26 try:27 return fn()28 except anthropic.APIStatusError as e:29 should_retry = e.response.headers.get("x-should-retry") # live: 'false' on every 4xx30 retry_after = e.response.headers.get("retry-after") # seconds, absent on spend-cap 42931 if isinstance(e, FATAL) or should_retry == "false" or i == attempts - 1:32 raise33 delay = float(retry_after) if retry_after else min(cap, base * 2 ** i) * (1 + random.random() * 0.25)34 print(f" retryable {e.status_code} {type(e).__name__} (request-id {e.request_id}); sleeping {delay:.2f}s")35 time.sleep(delay)36 except RETRYABLE as e: # connection/timeouts have no status37 if i == attempts - 1:38 raise39 time.sleep(min(cap, base * 2 ** i))404142def demo(label, fn):43 try:44 r = call_with_backoff(fn)45 print(f"{label}: ok -> {r}")46 except anthropic.APIStatusError as e:47 body = e.body if isinstance(e.body, dict) else {}48 err = body.get("error", {})49 print(f"{label}: HTTP {e.status_code} {type(e).__name__} type={err.get('type')} msg={err.get('message')!r} request_id={e.request_id}")50 live.log_request("anthropic", "POST", "/v1/messages", e.status_code, 0, f"error-handling demo {label}")51 except anthropic.APIConnectionError as e: # includes APITimeoutError52 print(f"{label}: connection problem {type(e).__name__}: {e}")535455demo("404 unknown model", lambda: client.messages.create(model="claude-does-not-exist", max_tokens=8, messages=[{"role": "user", "content": "Reply with OK."}]))56demo("400 bad temperature", lambda: client.messages.create(model=MODEL, max_tokens=8, extra_body={"temperature": 5}, messages=[{"role": "user", "content": "Reply with OK."}]))575859# NOTE: anthropic 1.7.0 removed the deprecated `temperature`/`top_p`/`top_k` keyword arguments from messages.create();60# send them with extra_body={...} if you still target a model that accepts them.61# Streaming: an `error` event can arrive after HTTP 200; the SDK raises inside the iteration.62def stream_demo():63 try:64 with client.messages.stream(model=MODEL, max_tokens=8, messages=[{"role": "user", "content": "Reply with OK."}]) as s:65 text = "".join(s.text_stream)66 print("stream ok:", text.strip(), "| stop_reason:", s.get_final_message().stop_reason)67 live.log_request("anthropic", "POST", "/v1/messages", 200, 0.00004, "error-handling demo stream")68 except anthropic.APIError as e: # mid-stream overloaded_error etc. → retry the whole request with backoff69 print("stream failed:", type(e).__name__, e)707172stream_demo()73