◉CLAUDE LABJP
●2.1.312 — Fixes Claude overwriting very large CLAUDE.md files and skills going missing after a Bash cd●API — The Messages API maximum request size grew from 32 MB to 128 MB (10/28)●SONNET 4.5 — Retires on the Claude API on 11/30, 31 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●API — In API version 2026-10-21, passing temperature, top_p or top_k returns a 400 error●NEW — I Put a Ten-Line Promise File at the To…●2.1.312 — Fixes Claude overwriting very large CLAUDE.md files and skills going missing after a Bash cd●API — The Messages API maximum request size grew from 32 MB to 128 MB (10/28)●SONNET 4.5 — Retires on the Claude API on 11/30, 31 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●API — In API version 2026-10-21, passing temperature, top_p or top_k returns a 400 error●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-08-01Advanced

Swapping Tools Mid-Conversation Without Losing Your Prompt Cache

Tools can now be added and removed between turns, but the tool block sits at the very front of the cache prefix. I measured 12 mutation patterns with fingerprints and built a stable-core plus volatile-tail registry with a guard that refuses unsafe changes.

Claude API138Prompt Caching6Tool Use9Architecture3Cost Optimization9

✦ Premium Article

I was trimming unused tools from a long-running session.

Twelve tools were still attached, several of which clearly would not be called again in the back half of the conversation. The beta for adding and removing tools between turns had landed, so I dropped four of them. Input tokens should have gone down.

They did not. They went up.

cache_read_input_tokens fell to zero, and the entire accumulated conversation history was being resent. What I thought was a savings had quietly invalidated the cache and made me pay for everything again.

The tool block sits at the very front

Prompt caching works on prefix matching. Everything from the start of the request up to the first divergence is eligible for a cache hit. Change one byte anywhere, and everything downstream misses.

The trouble is where tool definitions live in that ordering.

PositionBlockExpected churnBlast radius
1toolsShould be lowAll of system and messages
2systemLowAll of messages
3messages (history)Append-onlyLater turns
4messages (current)Every turnNone

The tool array has the widest blast radius in the whole request. It does not matter whether a hundred turns have piled up behind it — swap one tool and all hundred turns go with it.

The beta unlocking mid-conversation tool changes and those changes being cache-safe are two separate things. The API accepting the mutation does not mean the prefix survives it.

I had conflated the two. Reading the beta note, I took "prompt cache is maintained" to mean maintained across any change at all.

Measuring 12 mutations by fingerprint

Rather than reason about it, I measured it. Build a rolling hash chain over the tool array — one hash per prefix length — then compare the chains before and after a change.

# toolprefix.py — measure prefix fingerprints over a tool array
import hashlib
import json
 
def canon(obj):
    """Deterministic serialization: sorted keys, fixed separators."""
    return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
 
def prefix_chain(tools):
    """Return the rolling hash after each successive tool."""
    chain, h = [], hashlib.sha256()
    for t in tools:
        h = h.copy()               # without copy(), later updates bleed backwards
        h.update(canon(t).encode())
        chain.append(h.hexdigest())
    return chain
 
def shared_prefix_len(a, b):
    """How many leading elements two hash chains agree on."""
    n = 0
    for x, y in zip(a, b):
        if x != y:
            break
        n += 1
    return n

That h.copy() matters. Without it you keep mutating a single hasher and every entry ends up as the final digest. I spent an evening staring at a meaningless 100% match before spotting it.

The baseline is five tools shaped like a real setup.

def mk(name, props):
    return {
        "name": name,
        "description": f"{name} tool",
        "input_schema": {
            "type": "object",
            "properties": props,
            "required": sorted(props.keys()),
        },
    }
 
BASE = [
    mk("search_docs", {"query": {"type": "string"}, "top_k": {"type": "integer"}}),
    mk("read_file",   {"path": {"type": "string"}}),
    mk("write_file",  {"path": {"type": "string"}, "body": {"type": "string"}}),
    mk("run_tests",   {"suite": {"type": "string"}}),
    mk("post_report", {"channel": {"type": "string"}, "text": {"type": "string"}}),
]
 
base_chain = prefix_chain(BASE)
for label, mutate in CASES:
    n = shared_prefix_len(base_chain, prefix_chain(mutate(BASE)))
    print(f"{label:<26} {n}/{len(BASE)}  {n / len(BASE):.0%}")

Here is what twelve patterns produced on my machine.

MutationShared prefixRetentionVerdict
Append one tool at the tail5/5100%Safe
Rebuild with identical content5/5100%Safe
Remove the last tool4/580%Partial
Edit the last tool's description4/580%Partial
Add an optional property to the last tool4/580%Partial
Insert a tool in the middle2/540%Partial
Remove a middle tool2/540%Partial
Rename a middle tool2/540%Partial
Swap the second and third tools1/520%Partial
Prepend one tool at the head0/50%Total loss
Remove the first tool0/50%Total loss
Edit the first tool's description0/50%Total loss

Two of twelve preserved the prefix completely. Average retention was 48%.

The "Partial" rows are the trap. A 4/5 prefix match is worth nothing unless a cache_control breakpoint sits exactly at that boundary. Cache granularity is the breakpoint-delimited segment, not the individual tool.

If your only breakpoint is at the end of the tool array — the common setup — then only the two "Safe" rows actually survive. The other ten collapse to zero whether they scored 80% or 20%.

That was precisely my failure. When I trimmed unused tools, post_report had been sitting in the middle of the array.

✦

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
✦12 tool mutations measured by prefix fingerprint: only 2 of 12 preserved the prefix completely, average retention 48%
✦36 logically identical tool definitions produced 36 distinct fingerprints under plain JSON serialization — a 100% false-invalidation rate
✦Measured: the core split rescues only 3.69% of the bytes that actually move, and freezing the tool block breaks even against it at one mutation per fifteen turns
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-06-13
Claude Vision API in Production — Implementation Patterns for Image Analysis, PDF Processing, and OCR
Implementation patterns for taking Claude's vision capabilities to production: choosing between Base64, URL, and the Files API, native PDF processing, schema-enforced extraction with Tool Use, batch cost reduction, and error recovery — all with working code.
⬡ API & SDK2026-04-25
Claude API × Tauri 2: Building a Production Desktop AI App With Rust — Streaming, Tool Use, and Signed Distribution
A complete guide to shipping a production-grade desktop AI app with Tauri 2 and the Claude API: keychain-backed key storage, an SSE streaming bridge in Rust, Tool Use, and macOS/Windows signed distribution — with code you can copy.
⬡ API & SDK2026-04-17
Building a GitHub PR Review Bot with Claude API — From Webhook Signature Verification to Structured Reviews with Tool Use
Build a production PR review bot with Claude API and GitHub Webhooks. Enforce structured scoring and security scanning via Tool Use, then handle the parts that bite in production — signature checks that pass only locally, 202 responses that hide failures, debouncing, and cost tracking.