●COWORK — Claude Cowork expands to web and mobile with remote sessions, synced files, and a shared Chat and Cowork home across devices●M365 — Claude Cowork adds Microsoft 365 write tools to draft and send email, manage calendars, and update OneDrive and SharePoint files●MCP — Admins can provision MCP connectors org-wide through their identity provider, starting with Okta, with access granted on first login●CODE — Claude Code fixes SessionStart hook streaming in headless sessions so remote workers are not idle-reaped mid-hook●GOV — Claude Code and Claude Cowork are in public beta in Claude for Government Desktop on a FedRAMP High authorized environment●LIMITS — Claude Code weekly usage limits are increased by 50% through July 13●COWORK — Claude Cowork expands to web and mobile with remote sessions, synced files, and a shared Chat and Cowork home across devices●M365 — Claude Cowork adds Microsoft 365 write tools to draft and send email, manage calendars, and update OneDrive and SharePoint files●MCP — Admins can provision MCP connectors org-wide through their identity provider, starting with Okta, with access granted on first login●CODE — Claude Code fixes SessionStart hook streaming in headless sessions so remote workers are not idle-reaped mid-hook●GOV — Claude Code and Claude Cowork are in public beta in Claude for Government Desktop on a FedRAMP High authorized environment●LIMITS — Claude Code weekly usage limits are increased by 50% through July 13
Building Self-Healing AI Agents with Claude API — Error Detection, Auto-Recovery, and Graceful Degradation Patterns for Production
Learn how to build production-grade AI agents that automatically detect failures and self-heal using Claude API. Covers retry strategies, fallback chains, Supervisor patterns, and observability pipelines.
As an indie developer running my own apps and services, the thing that keeps me up at night isn't writing new features — it's what happens when something quietly breaks at 3 a.m. Billing validation, outbound API calls, automated responses — when any of these stall, the morning's revenue and reputation take the hit.
When I first started building agents with Claude API, I fell into the classic trap of writing demo code for the happy path. Rate limits, tool timeouts, model hallucinations — I wasn't designing under the assumption that they will happen.
Running an agent in production teaches you that AI-system failures are qualitatively different from ordinary server failures. A single 429 response doesn't just need a retry — it forces a four-way decision: retry as-is, switch to a different model, compress the context, or escalate to a human. Wrapping everything in a circuit breaker isn't enough.
A self-healing agent is one that classifies these AI-specific failures on its own and picks the right recovery strategy. The patterns I describe here come from running Dolice Labs (Claude Lab, Gemini Lab, Antigravity Lab, Rork Lab) in parallel as an indie operator, so the focus is operational rather than theoretical. Working TypeScript is included throughout.
Six Operational Gotchas Apple's, AWS's, and Anthropic's Docs Don't Mention
Before the patterns themselves, here are six things I had to learn the hard way running Claude API against real traffic. The SDK reference doesn't say these out loud, but you'll hit every one of them once you go past demo scale.
1. You'll see 529 (overloaded), not just 429
When classifying Anthropic.APIError, expect 529 "regional overload" in addition to rate-limit 429s. 529 usually arrives without a retry-after header, so reusing your 429 back-off logic isn't enough. In my experience, 529s tend to clear within a few minutes, so my rule is: exponential back-off up to 60 seconds, then switch to a different model if it still hasn't recovered.
2. On tool timeouts, return an error string — never an empty one
If you abort a tool call with AbortController, Claude assumes the call completed and waits for the next message. I learned the hard way that returning an empty string in tool_result triggers parsing crashes downstream. I now always return something like "ERROR: tool timed out after 30s, please proceed without this data". English error strings get interpreted by Claude more reliably than localized ones.
3. To resume a broken stream, preserve tool_use_id
If a stream: true request is cut off mid-stop_reason: tool_use, you have to rebuild the request with the matching tool_use_id in the next tool_result, or Claude loses track of context. I store these snapshots in a local SQLite table and replay them on reconnect.
4. A Sonnet Supervisor is more expensive than you'd expect
I initially built the Supervisor with Sonnet because "smart monitoring sounds good." Measured cost was around 3,000 tokens per incident, roughly ¥1.5 (about $0.01). At 1,000 incidents per day — easily reachable at small production scale — that's ¥1,500 daily, ¥45,000 monthly. Swapping the Supervisor to Haiku kept quality steady and cut the cost by roughly 75%. The Supervisor is doing classification, not creative work; Haiku is enough.
5. Context-compression summaries hallucinate
Asking a separate Claude call to summarize a long context works, but the summary itself hallucinates. I once shipped a system that swapped "succeeded" and "failed" tool results during summarization. The fix that worked: temperature: 0, plus a system prompt that says "summarize facts only, no inference, mark anything unclear as 'unknown'." Hallucinations dropped sharply.
6. Cloudflare Workers is a bad host for the agent loop
Dolice Labs runs on Cloudflare Workers + OpenNext, but Workers' CPU time limits (50 ms free, 30 s paid) don't accommodate long agent loops. Three self-healing retries alone will blow past it. I now run the agent body on Lambda, Cloud Run, or a small VM. Workers is great for result storage and lightweight state transitions, but the loop itself wants somewhere it can sit and breathe.
✦
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
✦Classify 7 categories of AI-agent-specific errors and assign the optimal recovery strategy to each — full production implementation
✦Working TypeScript code for the Supervisor pattern, fallback chains, and graceful degradation that you can drop into a real system
✦Hard-won indie-developer lessons distilled into observability and cost-vs-reliability trade-offs that keep you from getting paged at night
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.
The 7 Error Categories and Optimal Recovery Strategies
Production errors fall into seven distinct categories, each requiring a different recovery approach. Understanding this taxonomy is the foundation of effective self-healing.
1. Transient API Errors
Symptoms: 429 Rate Limit, 500 Internal Server Error, 503 Service Unavailable
Strategy: Exponential backoff with jitter (max 3 retries)
Key insight: Time resolves the problem. Retries succeed at a high rate
The most fundamental recovery strategy for transient API errors. Rather than simple retries, we add jitter (random variance) to exponential backoff to distribute load across the server.
Important: When the Anthropic API returns a retry-after header, always honor that value over your calculated backoff. The header in a 429 response indicates the server's recommended wait time.
Automatic Context Compression for Recovery
Long-running agents accumulate conversation history until they hit token limits. A self-healing agent automatically compresses context to continue processing.
The Supervisor Pattern for Multi-Agent Self-Healing
Single-agent self-healing has its limits — an agent stuck in an infinite loop can't recognize the problem on its own. The Supervisor pattern uses a separate agent to monitor workers and intervene when anomalies are detected.
When all recovery attempts fail, the system should aim for "operating with reduced functionality" rather than "broken." This is graceful degradation.
// graceful-degradation.ts — Progressive capability reductionclass GracefulDegradation { private client: Anthropic; constructor(client: Anthropic) { this.client = client; } async degrade( task: string, context: Anthropic.MessageParam[], failedCapabilities: string[] ): Promise<{ result: string; level: DegradationLevel }> { // Level 1: Try answering without tools if (failedCapabilities.includes("tools")) { try { const response = await this.client.messages.create({ model: "claude-opus-4-6", max_tokens: 4096, system: "External tools are temporarily unavailable. " + "Answer as accurately as possible using only your knowledge. " + "Clearly indicate where tool access would have been needed.", messages: [ ...context, { role: "user", content: `[System: Tools unavailable — knowledge-only mode] ${task}`, }, ], }); return { result: this.extractText(response) + "\n\n---\n⚠️ Some external data retrieval failed. " + "This response is based on AI knowledge. " + "Please verify with up-to-date sources.", level: "partial", }; } catch { // Fall through to Level 2 } } // Level 2: Fall back to a lighter model try { const response = await this.client.messages.create({ model: "claude-haiku-4-5", max_tokens: 1024, messages: [ { role: "user", content: `Please answer briefly: ${task}`, }, ], }); return { result: this.extractText(response) + "\n\n---\n⚠️ A simplified response due to temporary system limitations.", level: "minimal", }; } catch { // Fall through to Level 3 } // Level 3: Cached response or error message return { result: "We're sorry — the system is experiencing a temporary issue. " + "Please try again in a few minutes. " + "If the problem persists, contact support.", level: "unavailable", }; } private extractText(response: Anthropic.Message): string { return response.content .filter((b): b is Anthropic.TextBlock => b.type === "text") .map((b) => b.text) .join(""); }}type DegradationLevel = "full" | "partial" | "minimal" | "unavailable";
Two Operational Questions I Keep Asking Myself
After running the same agent stack across four Lab sites, I've boiled the day-to-day operational thinking down to two questions. The answers shape almost everything else about cost and reliability.
What should I watch before costs spike?
Beyond capping retries, I push three metrics to Datadog (or Cloudflare Analytics Engine) for every production agent: healings per minute, average tokens per healing, and cumulative cost attributable to healings. The alerting rule that survives review boards is the dumbest one: if daily cost goes 1.5× over the seven-day baseline, ping Slack. The MetricsCollector in this article emits all of that in a shape you can ship straight to a metrics endpoint.
What should still work if the Supervisor itself dies?
Classic "who watches the watchers" territory. My current answer is to keep the Supervisor on Haiku (cheap, simple, classification-only), then run a separate cron-triggered worker every 5 minutes that just pings the Supervisor. If the Supervisor isn't responding, individual Workers fall back to "independent mode" — they retry with a 10-second timeout, push results to a Pending queue, and let the Supervisor catch up once it's healthy again. I've had the Supervisor go down for 24 hours and lost zero data.
Stopping a Runaway Loop on Its Own — Token and Iteration Budget Guards
Everything so far has been self-healing in the "fix what broke" direction. There's a second failure mode that matters just as much: trying to fix things and never stopping.
The "no-progress loops" listed among the seven error categories get more likely as you layer on retries and context compression. A retry triggers another retry, and before long the orchestrator is cycling through the same decision and burning tokens with nothing to show for it. This is usually the one that wakes you at 3 a.m.
The fix is simple: hand the agent a budget up front and let it cut itself off when it runs out. Pairing an iteration cap with a token budget means that if one slips through, the other still catches it.
from dataclasses import dataclass@dataclassclass AgentBudget: max_input_tokens: int max_output_tokens: int max_iterations: int = 20 used_input: int = 0 used_output: int = 0 iterations: int = 0 def record(self, usage): self.used_input += usage.input_tokens self.used_output += usage.output_tokens self.iterations += 1 def exhausted(self) -> tuple[bool, str]: if self.iterations >= self.max_iterations: return True, f"hit iteration cap of {self.max_iterations}" if self.used_input >= self.max_input_tokens: return True, f"hit input-token cap of {self.max_input_tokens:,}" if self.used_output >= self.max_output_tokens: return True, f"hit output-token cap of {self.max_output_tokens:,}" return False, "" def nearly_done(self) -> bool: in_ratio = self.used_input / self.max_input_tokens out_ratio = self.used_output / self.max_output_tokens return max(in_ratio, out_ratio) > 0.8def run_with_budget(client, user_request: str, tools, budget: AgentBudget) -> str: messages = [{"role": "user", "content": user_request}] while True: stop, reason = budget.exhausted() if stop: # Don't die silently — surface why it stopped, with the latest state return f"[budget reached: {reason}] latest state: {messages[-1]['content']}" # Under 20% left? Tell the model to wrap up instead of branching further hint = "\n[Budget nearly spent. Do not open new branches; converge to an answer.]" if budget.nearly_done() else "" response = client.messages.create( model="claude-opus-4-6", max_tokens=min(4096, budget.max_output_tokens - budget.used_output), tools=tools, system="You manage the task." + hint, messages=messages, ) budget.record(response.usage) if response.stop_reason == "end_turn": return response.content[0].text messages.append({"role": "assistant", "content": response.content}) # Appending tool results to messages is identical to the loop shown earlier
The key detail is that when the budget runs out you return reason plus the partial state instead of throwing a silent exception. Feed reason straight into the MetricsCollector from the observability section, and the next morning's dashboard tells you at a glance which agent is eating its budget.
As an indie developer running several sites and app backends by myself, the scenario I fear most is a nightly job spinning out of control and spiking the bill by morning. Since I started passing an iteration cap, I've at least ruled out the "wake up to a huge bill" failure mode.
Looking back
Building self-healing AI agents is an essential skill for delivering stable AI services in production. By combining the seven error categories, the Supervisor pattern, fallback chains, and graceful degradation covered in this guide, you can build agent systems that run reliably around the clock without human intervention.
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.