●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 /subtask●LIMITS — 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 own●MCPBG — 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_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — 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 $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October●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 /subtask●LIMITS — 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 own●MCPBG — 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_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — 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 $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Absorbing Claude API 529 Overloaded in Production — Resilience Patterns from a 50M-Download Indie Studio
529 Overloaded won't go away with a naive exponential backoff. Drawing on lessons from 50 million app downloads, this piece walks through queue-based absorption, model-aware fallback, and circuit-breaker design with working code.
There are windows in the day when Anthropic's status page is solid blue but my own app is taking 529 after 529. I'm Masaki Hirokawa, an artist and indie developer running a personal app business since 2014 — collectively over 50 million downloads across iOS and Android. Once I started embedding Claude API into production, the most stubborn problem I hit was exactly this: my own quota had plenty of headroom, yet bursts of 529 kept landing on the same users. Wrapping the call in the same exponential backoff I had used for 429 only made it worse — the app felt frozen and users started reporting that buttons "did nothing."
529 Overloaded is an error you don't dodge. You absorb it. Below are the production patterns I run today in my wallpaper, photo, and writing apps, with concrete code and the operational metrics I watch.
529 lives on a different axis from your own congestion
429 and 529 sit next to each other in the status code table but behave very differently. 429 fires when your request rate or token usage hits the limit on your plan — slow yourself down and it resolves. 529 fires when Anthropic's platform is temporarily oversubscribed, so reducing your own traffic does nothing. You have to wait it out, route around it, or pull the user out of the synchronous path.
On my apps the empirical pattern is sharp. Over the last 30 days, 529 events made up 4.2% of failed calls overall, but 78% of those concentrated into the UTC 13:00–16:00 peak window. The 429 rate in the same window was flat. That divergence is the basis for combining model fallback with a time-of-day heuristic, which I'll come back to below.
First, classify the 529s you must NOT retry
The instinct is to wrap every 529 in exponential backoff. That breaks in three concrete cases I have hit:
First, blocking retries inside the synchronous UI path. Users will not sit through three rounds of exponential backoff; their experience is a frozen screen. Second, retrying after a streaming call has already delivered partial output. The follow-up call replays text the user already saw. Third, retrying a 529 that fired during tool_result submission. That path mutates conversation history if you submit a malformed sequence, and you need a dedicated repair branch that preserves tool_result block integrity.
The classifier needs to know which path you're on:
For sync_ui, try once briefly and then hand the job off to the async queue. Pair that handoff with a placeholder UX that tells the user their draft is being processed in the background — covered next.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦The difference between 429 and 529, and the three cases where you must NOT retry
✦An async queue plus placeholder UX that absorbs 529 without freezing the user
✦A Sonnet-to-Haiku fallback with per-job-kind quality preservation
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
An async queue plus placeholder UX turns waiting into "non-waiting"
The biggest absorption lever is decoupling user action from Claude's response. The instant the user taps the button, return "received" and free the UI; deliver the result via push notification or an in-app progress banner. I run this on Cloudflare Queues and Workers KV, but the shape is platform-agnostic.
The queue worker calls Claude, retries 529 transparently, and writes the final result back to KV. The user only ever sees "it took a bit longer than usual," and never a raw error. After moving my wallpaper app's AI comment-generation flow onto this async path, user-facing error exposure dropped by roughly 87%.
On the placeholder UX, the key is conveying "something is actually moving." I show three concrete phases (received → generating → refining) tied to real processing milestones rather than a single spinner. Support tickets about "the app is broken" dropped sharply once those phases were visible.
Sonnet → Haiku as a temperature-aware fallback
When 529 lingers on Sonnet during peak hours, switching to Haiku absorbs a meaningful share of the load. In my measurements, Haiku stayed responsive more than 60% of the time during windows when Sonnet was intermittently returning 529. The queues appear to be operationally independent for that subset.
That said, blind switching erodes quality for some jobs, so I run a three-stage fallback:
Stage 1 retries Sonnet once with a short backoff. Stage 2 consults a per-job-kind table to decide whether Haiku is allowed. Stage 3, if Haiku also stalls, asks the user whether the job can be deferred to the next day. Long-form writing and code generation stay Sonnet-only; tagging, classification, and short summaries are happy on Haiku.
In practice, my photo app routes 29% of jobs through Haiku because tasks like "album title candidates" and "wallpaper keyword tagging" hold up beautifully there. Writing-app outline generation stays Sonnet-only — I would rather wait it out in the async queue than ship a weaker outline.
A circuit breaker decides "when to give up"
If 529 persists for minutes and you keep retrying, your own queue swells until end-to-end latency for everyone gets worse. A circuit breaker stops calls during the worst patches.
import timefrom dataclasses import dataclass, field@dataclassclass CircuitBreaker: failure_threshold: float = 0.5 window_seconds: int = 60 cooldown_seconds: int = 90 samples: list = field(default_factory=list) opened_at: float | None = None def allow(self) -> bool: now = time.time() if self.opened_at is not None: if now - self.opened_at < self.cooldown_seconds: return False self.opened_at = None self.samples.clear() return True def record(self, success: bool): now = time.time() self.samples = [(t, s) for (t, s) in self.samples if now - t < self.window_seconds] self.samples.append((now, success)) if len(self.samples) < 10: return failures = sum(1 for (_, s) in self.samples if not s) if failures / len(self.samples) >= self.failure_threshold: self.opened_at = now
While the breaker is open, requests aren't failed immediately — they wait 90 seconds inside the queue and try again. When the breaker opens we also fire a Slack notification so a human can correlate against Anthropic's status page and Cloudflare's Workers Analytics in under 30 seconds.
How you tell the user shapes retention
The wording you ship when 529 (or 429) bubbles up matters as much as the technical absorption. Early on I showed a single generic "service is busy, please try again later" string — and watched my day-7 retention drop by about 4 points after a particularly bad week. Splitting that into three context-aware messages kept the retention hit under ~1.8 points for similar incidents.
The first message — for a 529 that escaped on the sync path — leads with "we saved your draft, a notification will follow." Confirming that nothing was lost is the first job. The second — for a long async wait — says "this is taking a little longer than usual; we'll notify you when it's ready," without promising a specific time. The third — for a job downgraded to Haiku — explicitly says "generated with the lightweight model; regenerate if you aren't happy." Hiding the downgrade is what erodes trust.
What to monitor, and what belongs in your SLOs
The four metrics I keep on an hourly dashboard:
The first is the raw 529 rate (count over the last 60 minutes divided by request count). It moves with Anthropic, not with you, so it's situational awareness rather than an SLO. The second is user-exposure rate — the percentage of 529s that actually surfaced to the user as an error string. My SLO is below 0.5%; breaching it triggers a review of queue capacity and breaker thresholds. The third is Haiku-fallback rate. If it stays above 35%, quality risk is real and I revisit the per-job-kind table. The fourth is p95 queue dwell time. My target is under 25 seconds; above that, the placeholder UX copy changes to set expectations.
Without those four, the only knob left when 529 hits is "retry more," which guarantees the user experience gets worse.
What to try this week
You won't eliminate 529, but you can almost always push it out of view. Start by checking your logs for 529s that landed on a synchronous UI path. If any are present, peel that one route off into an async queue this week — it's the single change that moved the needle most for my apps. Thanks for reading.
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.