CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/API & SDK
API & SDK/2026-04-26Intermediate

Decoding Claude's 'Spanner Temporarily Unavailable' Error and How to Handle It

The 'Spanner temporarily unavailable' error occasionally appears in Claude API and Claude.ai responses. This guide unpacks what the message reveals about Anthropic's infrastructure and walks through practical retry strategies that production teams actually use.

Claude API115Error Handling5SpannerTroubleshooting11Retries2Reliability5

import { Callout } from '@/components/ui/callout';

If you have built anything against the Claude API for more than a few weeks, you have probably seen this:

Error: spanner temporarily unavailable

The message is short, the HTTP status varies between 503 and 500 depending on the path, and the same prompt usually succeeds twenty seconds later. That makes it hard to reproduce and even harder to decide whether the bug is on your side or Anthropic's.

The wording itself tells us something about Anthropic's infrastructure, and that reading points straight at the retry patterns that have actually held up in production for me. None of this is in the official docs; it is the kind of knowledge you accumulate by shipping real systems on top of Claude.

What "Spanner" Tells Us

As soon as the word "Spanner" appears, you can infer something about the architecture. Spanner is Google Cloud's globally distributed database, which strongly suggests parts of Anthropic's stack are deployed on GCP and rely on Spanner for metadata storage. In other words, this error is almost never about the model itself failing. It is about the metadata layer next to the model briefly stalling.

In practice, you tend to see this error during:

  • saving conversation history or session state
  • API key validation or usage metering
  • reading attachments through Projects or Files APIs
  • looking up billing-related data

Knowing this changes how you respond: the retry pattern that works for "model is overloaded" is different from the one that works for "metadata layer is briefly stalled."

First Triage: Is It You or Is It Them?

Before changing any code, do these three checks.

First, open Anthropic Status. Spanner-flavored issues often show up as a "Partial outage" and tend to recover within thirty to sixty minutes.

Second, capture the request-id header from the failing call. Without that ID, support cannot meaningfully investigate. I make it a habit to log the request ID alongside any non-200 response.

Third, audit your own retry loop. Spanner errors are transient, so a simple, well-behaved backoff usually resolves them on its own. The trap is the opposite: an unbounded retry loop will hammer the API and trip your account into a separate rate-limit error, which then masks the original cause.

A Retry Pattern That Actually Holds Up

Exponential backoff with jitter is the pattern I run in production. Spanner errors typically recover within seconds to tens of seconds, so starting with a small delay and growing it works well.

import anthropic
import time
import random
 
client = anthropic.Anthropic()
 
def call_claude_with_retry(messages, model="claude-sonnet-4-6", max_retries=5):
    """Wrapper that survives transient errors like 'spanner temporarily unavailable'."""
    for attempt in range(max_retries):
        try:
            response = client.messages.create(
                model=model,
                max_tokens=1024,
                messages=messages,
            )
            return response
        except anthropic.APIStatusError as e:
            error_text = str(e).lower()
            is_transient = (
                "spanner" in error_text
                or "temporarily unavailable" in error_text
                or e.status_code in (502, 503, 504)
            )
            if not is_transient or attempt == max_retries - 1:
                raise
            wait = (2 ** attempt) + random.uniform(0, 1)
            print(f"Transient error, retrying in {wait:.1f}s...")
            time.sleep(wait)
    raise RuntimeError("max retries exceeded")

Two details deserve attention. The first is that I match on the literal substring spanner. The SDK does not currently expose a typed exception specifically for this case, so reading the message body is the practical move. The second is the jitter: without it, multiple instances retrying in lockstep will create a secondary spike that can trigger another wave of failures.

When Backoff Alone Isn't Enough

If retries do not clear the error, or if you are seeing it intermittently for hours, work through these in order.

  1. Switch models. Sonnet 4.6 sometimes fails while Haiku 4.5 succeeds. The two appear to land on slightly different infrastructure segments.
  2. Switch regions or providers. If you have access to Vertex AI or AWS Bedrock, those alternative routes often clear when the direct API is degraded. For business-critical systems, having Bedrock as a fallback is the cheapest insurance you can buy.
  3. Reduce the request footprint. Temporarily lower your token count or attachment size per request. Smaller payloads put less pressure on the metadata layer and frequently start succeeding before larger ones do.
  4. Open a support ticket with the request ID. If you have done all of the above and the error persists, contact Anthropic support. Include the request ID, the timestamp in UTC, and the rough frequency. With those three, investigations move much faster.

How to Surface This in Your Product

If you are building something user-facing on top of Claude, you do not want to leak "spanner temporarily unavailable" to end users. On Claude Lab and the sister sites I run, the rule is simple:

  • Retry internally, and tell users something gentle like "we're a little busy right now, please give it five seconds"
  • If the retry still fails, show a generic "please try again" and put the technical detail in server logs
  • For important form submissions, stash the user's input locally before retrying so a failed call does not destroy what they typed

You cannot drive the underlying error rate to zero, but you can drastically reduce the pain users feel.

Distinguishing Spanner Errors from Their Cousins

Finally, a quick taxonomy of related errors that look similar at a glance:

  • overloaded (429/529) — model-side congestion; backoff helps but capacity has to recover server-side
  • rate_limit_exceeded (429) — your API key is over budget; fix usage, not retry logic
  • internal_server_error (500) — model-side fault; Anthropic owns this fully
  • spanner temporarily unavailable (503/500) — metadata layer stalling; the topic of this article

When several of these appear together, it is easy to mistake one for another. Logging both error.type and error.message makes the post-mortem much easier.

Next Step

If you have not yet, add exponential backoff with jitter to your Claude integration today. The perceived stability of your product will improve immediately, and you will stop chasing ghost incidents that a five-line wrapper would have absorbed.

Yes, this is on Anthropic's side

Let's lead with the conclusion. This error is an Anthropic-side (or, more precisely, an underlying Google Cloud) transient issue. It is not a bug in your code, your authentication, or your request format.

A typical response body looks like this:

{
  "type": "error",
  "error": {
    "type": "api_error",
    "message": "Spanner temporarily unavailable. Please retry after a brief delay."
  }
}

The HTTP status code is usually 503 Service Unavailable or 529 Overloaded — both of which signal "the server can't process this right now."

But I want to push back on the obvious instinct, which is to just retry blindly. I tried exactly that during my first incident and made things measurably worse, both for Anthropic and for myself.

Why a naive retry loop is dangerous

What makes this error sneaky is that it often happens at a scale too small to ever land on the Anthropic Status Page. The three incidents I've witnessed each lasted somewhere between 30 seconds and 5 minutes, in localized regions, with no public acknowledgment afterward.

If your code is running a tight while True: retry() during one of these moments, three bad things happen at once:

  • You add load to a struggling backend — slowing recovery instead of helping it.
  • Your own connection pool drains — healthy requests start queueing behind dead ones.
  • Your token bill spikes — every retry that gets a non-cached response is billed.

In my first incident, I generated 8x my normal request volume in 5 minutes. I wasn't helping Anthropic, and I was paying for the privilege.

The three-tier retry pattern I now use

What I run today is a layered strategy. Python below, but the shape transfers cleanly to any language:

import time
import random
from anthropic import Anthropic, APIError, APIStatusError
 
client = Anthropic()
 
def call_claude_with_resilience(messages, model="claude-sonnet-4-6"):
    """Three-tier retry with circuit breaker behavior."""
 
    # Tier 1: instant retry for transient blips
    max_immediate_retries = 2
    # Tier 2: exponential backoff for short outages
    max_backoff_retries = 3
    # Tier 3: fallback to a different model
    fallback_model = "claude-haiku-4-5-20251001"
 
    last_error = None
 
    # Tier 1
    for attempt in range(max_immediate_retries):
        try:
            return client.messages.create(model=model, messages=messages, max_tokens=4096)
        except APIStatusError as e:
            if e.status_code in (503, 529) and "spanner" in str(e).lower():
                last_error = e
                time.sleep(0.2 + random.random() * 0.3)
                continue
            raise
 
    # Tier 2: exponential backoff with jitter
    for attempt in range(max_backoff_retries):
        delay = (2 ** attempt) + random.random()
        time.sleep(delay)
        try:
            return client.messages.create(model=model, messages=messages, max_tokens=4096)
        except APIStatusError as e:
            if e.status_code in (503, 529):
                last_error = e
                continue
            raise
 
    # Tier 3: model fallback + alert
    try:
        notify_ops("Spanner error fallback triggered")
        return client.messages.create(model=fallback_model, messages=messages, max_tokens=4096)
    except APIStatusError:
        # Real outage. Bubble the error up.
        raise last_error

Three things matter about this shape:

Tier 1 is two fast retries with a 200–500ms delay. Most "spanner" errors I see are pure transient blips that clear within a single second. If you stop here, you've handled 90% of cases.

Tier 2 has exponential backoff with jitter. If multiple processes hit the same incident at once and all retry on the same schedule, you create a thundering-herd that prevents the backend from ever recovering. Jitter is non-negotiable.

Tier 3 is a model downgrade. I've personally observed cases where Sonnet was failing while Haiku stayed healthy on the same account. A slightly degraded answer is much better than a fully unavailable service from the user's perspective.

Batch jobs deserve a different philosophy

For interactive workloads (chatbots, in-app generation), the pattern above is what you want. For overnight batch jobs, I take a much more relaxed approach: fail fast, defer to tomorrow.

def process_batch(items):
    failed = []
    for item in items:
        try:
            result = call_claude_with_resilience(item.messages)
            save(item, result)
        except APIStatusError as e:
            if e.status_code in (503, 529):
                failed.append(item)
            else:
                raise
 
    if failed:
        # Push to the Anthropic Batch API or just retry tomorrow
        enqueue_for_retry(failed, delay_hours=12)
        log.info(f"Deferred {len(failed)} items due to API instability")

For anything that genuinely tolerates a 24-hour SLA, the Anthropic Batch API absorbs these errors automatically and is what I default to now.

Build your own status page

The third lesson from my incidents: Anthropic's Status Page is too coarse to catch these. A 5-minute regional blip almost never makes the public log.

To compensate, I added a small set of metrics on my side:

  • Claude API error rate in 5-minute windows, per model
  • A counter for any response containing the string "spanner"
  • A timeline of every fallback event (Tier 3 trigger)

The "spanner counter with a Slack alert" is the one that's earned its keep most. It catches incidents 10–30 minutes before any community Tweet, which gives me a head start on customer communication.

How to tell "outage" from "your design"

A short caveat. If you're seeing this error 5+ times per hour from the same API key, the probability shifts away from "Anthropic transient" and toward "your request shape is provoking it."

The two patterns I've seen cause this most often:

  1. Sending extremely long contexts (500K+ tokens) in a tight loop.
  2. A broken Prompt Caching setup that's silently sending the full prompt every call.

Logging the X-Cache-Hit header and watching cache hit rates is the cheapest way to catch the second one. If your hit rate is dramatically lower than expected, you've probably broken your cache key inadvertently.


The next time you see this error, start with Tier 1: two fast retries with jitter. If the error keeps showing up, invest a day in implementing the full three-tier pattern. The "I sleep through the night" benefit pays for the implementation cost many times over.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API & SDK2026-06-20
Running Subagents in Parallel Without One Failure Sinking the Whole Run
A fan-out / fan-in design for running several subagents in parallel, covering token budgeting, a result contract, and partial-failure handling. Includes an implementation where one branch can fail without stopping the rest, plus measured numbers.
API & SDK2026-04-20
Three Hidden Pitfalls When Implementing Claude API Streaming
Real-world lessons from building with Claude API streaming: runtime environment mismatches, error handling gaps, and silent token cost overruns — with working TypeScript examples.
API & SDK2026-03-14
Claude API Streaming & Tool Use in Production — Patterns for Parallel Calls, Error Handling, and Retry Strategies
Master production-grade streaming and tool use patterns with Claude API. Learn parallel tool calling, intelligent error handling, resilient retry strategies, and resource optimization.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →