◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-04-26Advanced

Replay-Driven Testing for Claude API: A Production Pattern for Recording and Replaying Responses

A production-grade design for stabilizing Claude API tests by recording and replaying real responses. Covers cassettes for Messages, Streaming, Tool Use, CI integration, and incident replay.

api-sdk13testing6production111claude-api83ci-cd8

✦ Premium Article

"My tests come back red because Claude phrased the answer slightly differently again, and I can't tell whether something actually broke or the model just had a different mood today." Most teams who put Claude API into production hit this wall. I have personally spent half a day chasing a refactor regression that turned out to be a one-word change in Claude's response — the test was checking the wrong thing, but I didn't know that until I had pulled the entire stack apart.

This article walks through the cassette-based testing pattern that Ruby's VCR, Python's VCR.py, and JavaScript's Polly.js made famous, adapted for every Claude API surface: Messages, Streaming, and Tool Use. Instead of mocking, you call the real Claude API exactly once, save the response to disk, and replay it forever after. The result is deterministic tests, near-zero CI cost, and the ability to reproduce production failures locally.

Why ordinary mocks fall short

The first instinct is to patch messages.create with unittest.mock and return a hand-written response object. I started there too. After three months of operating a multi-agent system on top of it, I gave up. There are three concrete reasons why hand-rolled mocks don't scale.

Mocked responses drift from reality. Claude's response shape gets extended regularly. New stop_reason values appear, content blocks get new types, the usage object gains fields. A hand-written mock is a snapshot frozen in time, so it lies to your tests every time the SDK ships an update. Tests stay green while production breaks — the worst possible failure mode.

Tool Use sequences are tedious to fake. A single tool-using test typically involves at least two round trips: Claude asks for a tool call, your code runs the tool, you send the result back, Claude returns the final answer. Hand-mocking that flow forces you to manually thread tool_use_id, role ordering, and content block types — every one of which is easy to get subtly wrong.

Streaming responses are nearly impossible to mock by hand. Producing a faithful sequence of message_start, content_block_start, content_block_delta, content_block_stop, message_delta, and message_stop events in the right order is so error-prone that recording an actual stream and replaying it byte-for-byte is dramatically safer.

The cassette pattern dissolves all three problems with one rule: hit the real API once, save what came back, and replay it for the rest of the test's life.

The cassette architecture, top to bottom

Before any code, here is the layered design I use in production. Three layers, each replaceable.

The cassette store is just a directory of .json or .jsonl files under tests/cassettes/, one per test. The one-test-one-cassette rule is non-negotiable: it makes "what does this test record?" a grep query.

The interceptor patches your Anthropic client at runtime. If a recording exists for the request, return it; otherwise call the real API and persist the response. A record_mode flag — once, new_episodes, or none — lets you control whether new recordings are allowed.

The request matcher decides which recorded response matches the current request when a cassette holds multiple recordings. I hash messages, model, system, the tool definitions, and the sampling settings into a short key. Anything left out of that hash can change without changing the key — a trap I walked into, and describe further down.

Keeping these layers separate matters when you later want to change the cassette format, relax the matcher, or add encryption — none of those forces you to rewrite the tests themselves.

✦

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
✦You will see why leaving system out of the recording key lets a rewritten prompt replay a stale response while the test stays green, and how to close that gap
✦You will learn how to apply the cassette pattern from VCR.py and Polly.js to every Claude API surface — Messages, Streaming, and Tool Use — with working code
✦You'll get a workflow for replaying production failures locally, so untouchable 'I can't reproduce it' bugs become reproducible in minutes
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.

or
Unlock all articles with Membership →
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 →

Related Articles

⬡ API & SDK2026-05-05
The Real Cost of Claude API Extended Thinking in Production — ROI Data by Task Type
Three months of measured cost, quality, and speed data for Extended Thinking across five task categories. Learn exactly when extended thinking is worth it—and when it's not.
⬡ API & SDK2026-03-27
Building LLM Evaluation Pipelines with Claude API — Claude-as-Judge, Prompt A/B Testing, and Quality Scoring Patterns
Designing and implementing LLM evaluation pipelines on the Claude API — Claude-as-Judge, prompt A/B testing, quality scoring, and regression testing for production applications.
⬡ API & SDK2026-05-26
Stabilizing Claude API Structured Responses in Production — Notes on tool_use, JSON Schema, and Layered Validation
Getting Claude to return JSON takes a few lines. Keeping that JSON usable in production is a different problem. Here is the layered design I landed on after running a wallpaper classification pipeline through Claude API, built around tool_use, JSON Schema, and domain validation.