Troubleshooting & Reliability Basics
10 examples to get you started with Troubleshooting & Reliability - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with Troubleshooting & Reliability - 7 basic and 3 intermediate.
pip install anthropic.ANTHROPIC_API_KEY in your environment before running any example.logging module is used throughout; no extra logging library is required.try/except and the anthropic client is assumed; nothing else is needed to follow along.Wrap the client call so every request and response is logged before anything else happens.
import logging
import time
import anthropic
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("claude_client")
client = anthropic.Anthropic()
def call_claude(prompt: str) -> anthropic.types.Message:
started = time.monotonic()
logger.info("claude.request", extra={"prompt_len": len(prompt)})
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=512,
messages=[{"role": "user", "content": prompt}],
)
elapsed_ms = (time.monotonic() - started) * 1000
logger.info(
"claude.response",
extra={"elapsed_ms": elapsed_ms, "stop_reason": response.stop_reason},
)
return responseelapsed_ms is the single most useful field for spotting latency creep before it becomes an outage.extra={} keeps structured fields separate from the log message so they can be queried later.Related: Understanding Why Claude Agents Fail in Production - the failure taxonomy this logging feeds into
The anthropic SDK raises typed exceptions; catching the base class alone hides useful detail.
import anthropic
client = anthropic.Anthropic()
try:
client.messages.create(
model="claude-sonnet-5",
max_tokens=256,
messages=[{"role": "user", "content": "Summarize this incident."}],
)
except anthropic.RateLimitError as e:
print("rate limited:", e.status_code)
except anthropic.APITimeoutError as e:
print("timed out:", e)
except anthropic.APIStatusError as e:
print("api error:", e.status_code, e.response)
except anthropic.APIConnectionError as e:
print("connection error:", e)RateLimitError is a subclass of APIStatusError, so order matters: catch it before the more general class.APITimeoutError and APIConnectionError are both transport-level, not API-level, failures.e.status_code on APIStatusError tells you exactly which HTTP status came back.Exception here would hide which failure family you're dealing with.Every API response carries a request ID that Anthropic support (and your own RCA template) will ask for.
import anthropic
client = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=256,
messages=[{"role": "user", "content": "Hello"}],
)
request_id = response._request_id
except anthropic.APIStatusError as e:
request_id = e.request_id
print(f"request {request_id} failed with status {e.status_code}")APIStatusError exceptions.Turn a raw exception into one of the failure families from the section's mental-model page.
import anthropic
def classify_error(exc: Exception) -> str:
if isinstance(exc, anthropic.RateLimitError):
return "capacity"
if isinstance(exc, (anthropic.APITimeoutError, anthropic.APIConnectionError)):
return "lifecycle"
if isinstance(exc, anthropic.APIStatusError) and exc.status_code >= 500:
return "capacity"
if isinstance(exc, anthropic.APIStatusError):
return "contract"
return "unknown"APIStatusError that isn't rate-limiting or 5xx is treated as a contract issue, like a bad request shape.Related: Diagnosing 429 Rate-Limit Errors and Quota Exhaustion Under Load - deep dive on the capacity family
Token counts are on every response and are your earliest signal of cost or context problems.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=512,
messages=[{"role": "user", "content": "Explain vector clocks briefly."}],
)
usage = response.usage
print(f"input_tokens={usage.input_tokens} output_tokens={usage.output_tokens}")usage.input_tokens and usage.output_tokens are always present on a successful response.input_tokens for the same logical request often signals prompt or context growth.Related: Prompt Cache Miss Storms: Diagnosing Sudden Cost and Latency Spikes - what to do when token usage spikes unexpectedly
A cheap, low-token call you can run on a schedule to confirm the API path is healthy end to end.
import anthropic
client = anthropic.Anthropic()
def health_check() -> bool:
try:
client.messages.create(
model="claude-haiku-4-5",
max_tokens=1,
messages=[{"role": "user", "content": "ping"}],
)
return True
except anthropic.APIError:
return Falsemax_tokens to keep this call nearly free to run frequently.anthropic.APIError is the shared base class for both status errors and connection errors, useful for a simple boolean check.When an agent uses tools, log the raw tool-use block before you attempt to parse its arguments.
import json
import logging
logger = logging.getLogger("claude_client")
def log_tool_use(response) -> None:
for block in response.content:
if block.type == "tool_use":
logger.info(
"claude.tool_use",
extra={"tool_name": block.name, "raw_input": json.dumps(block.input)},
)block.input from the SDK is already a parsed dict for well-formed calls, but capturing it as JSON keeps the log format consistent.Related: Handling Malformed Tool-Use JSON and Schema Validation Failures - catching and repairing bad tool-call output
A single wrapper that ties the earlier pieces together into one diagnostic call site.
import logging
import time
import anthropic
logger = logging.getLogger("claude_client")
client = anthropic.Anthropic()
def diagnostic_call(prompt: str) -> anthropic.types.Message | None:
started = time.monotonic()
try:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=512,
messages=[{"role": "user", "content": prompt}],
)
except anthropic.APIError as e:
elapsed_ms = (time.monotonic() - started) * 1000
family = classify_error(e)
request_id = getattr(e, "request_id", None)
logger.error(
"claude.failure",
extra={"family": family, "elapsed_ms": elapsed_ms, "request_id": request_id},
)
return None
elapsed_ms = (time.monotonic() - started) * 1000
logger.info(
"claude.success",
extra={"elapsed_ms": elapsed_ms, "tokens": response.usage.output_tokens},
)
return responseclassify_error (example 4) is reused here rather than duplicated, keeping the taxonomy consistent across the codebase.None on failure, rather than re-raising, is a deliberate choice for call sites that can degrade gracefully.return None for a re-raise if the caller needs to fail loudly instead.Track a rolling error rate in memory as a first monitoring signal, before you wire up a real metrics backend.
from collections import deque
import time
class RollingErrorRate:
def __init__(self, window_seconds: int = 300):
self.window_seconds = window_seconds
self.events: deque[tuple[float, bool]] = deque()
def record(self, success: bool) -> None:
now = time.monotonic()
self.events.append((now, success))
cutoff = now - self.window_seconds
while self.events and self.events[0][0] < cutoff:
self.events.popleft()
def error_rate(self) -> float:
if not self.events:
return 0.0
failures = sum(1 for _, success in self.events if not success)
return failures / len(self.events)record(success) is called from the diagnostic wrapper in example 8, once per call.error_rate() cross a threshold (e.g. 5%) as your first automated signal that something changed.A minimal script for the first step of any incident: reproduce the failure with full request and response visibility.
import logging
import anthropic
logging.basicConfig(level=logging.DEBUG)
client = anthropic.Anthropic()
def replay(prompt: str, model: str = "claude-sonnet-5") -> None:
try:
response = client.messages.create(
model=model,
max_tokens=512,
messages=[{"role": "user", "content": prompt}],
)
print("SUCCESS:", response.stop_reason, response.usage)
except anthropic.APIStatusError as e:
print("FAILED:", e.status_code, e.request_id, e.response.text)
if __name__ == "__main__":
replay("The prompt that was reported as failing")logging.DEBUG on the SDK's own logger surfaces the raw HTTP request and response, which is often the fastest way to spot a malformed header or payload.Related: Retry and Exponential Backoff Strategies for Transient Claude API Failures - what to build once you can reliably reproduce a transient failure Related: Troubleshooting & Reliability Best Practices - the checklist to apply once your basics are in place
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, pricing, and SDK versions move quickly - verify current specifics at platform.claude.com/docs before relying on them.
Reviewed by Chris St. John·Last updated Jul 19, 2026