◉CLAUDE LABEN
●2.1.283 — 週末の Claude Code リリースはなく、最新は 9月25日の 2.1.283 のまま。権限モード未設定時の既定が auto に変わった点は、許可ルールとの照合が要ります●PLANS — Pro と Team Standard の既定モデルが Sonnet から Opus へ。Claude Code 2.1.280 の変更で、プラン選びの前提が変わっています●10/07 — Claude Desktop / Cowork の管理構成キー旧綴りの受付終了まで残り9日。締切後は読み込みが fail-closed になります●AUTO — 「許可ルールに書いたコマンドが auto モードで拒否される」という問いが出ています。呼ぶ形を許可ルールに揃える話題です●NEW — 既定の Opus が 5.5 に変わった週、別名のまま残していた3箇所の確認●CURSOR — Cursor 経由で Claude を使うとクレジットがモデル原価に比例して減る構造。1日50件を超えるなら定額の Claude Code Pro が安定という整理が出ています●2.1.283 — 週末の Claude Code リリースはなく、最新は 9月25日の 2.1.283 のまま。権限モード未設定時の既定が auto に変わった点は、許可ルールとの照合が要ります●PLANS — Pro と Team Standard の既定モデルが Sonnet から Opus へ。Claude Code 2.1.280 の変更で、プラン選びの前提が変わっています●10/07 — Claude Desktop / Cowork の管理構成キー旧綴りの受付終了まで残り9日。締切後は読み込みが fail-closed になります●AUTO — 「許可ルールに書いたコマンドが auto モードで拒否される」という問いが出ています。呼ぶ形を許可ルールに揃える話題です●NEW — 既定の Opus が 5.5 に変わった週、別名のまま残していた3箇所の確認●CURSOR — Cursor 経由で Claude を使うとクレジットがモデル原価に比例して減る構造。1日50件を超えるなら定額の Claude Code Pro が安定という整理が出ています
記事一覧/API & SDK
⬡ API & SDK/2026-09-28中級

Claude が 529 を返した夜、頼みのフォールバック先まで落ちました — cache_control は境界で剥がす

Claude API のフォールバック先で TypeError が出た原因は、cache_control 付きの content block をそのまま他社モデルへ渡していたことでした。境界で剥がす関数と送る直前の検問、そして発火する前にフォールバック経路を試す運用を書き残します。

Claude API123prompt caching2cache_controlfallback8LangChain

ストア掲載文の多言語チェックを夜のバッチに任せていた頃、Claude API が 529 を返し続ける時間帯がありました。混雑時の 529 は仕様として織り込んでおり、他社モデルへ切り替えるフォールバックも用意してありました。翌朝ログを開くと、切り替わった先で TypeError が出ていて、結果は一件も残っていませんでした。

胃が重くなったのは、Claude 側の混雑ではなく、私が書いた保険のほうが先に破れていたと気づいたときです。半年ほど前に組んだフォールバックは、その夜まで一度も発火したことがなかったのです。

原因は、プロンプトキャッシュのために付けていた cache_control を、他社モデルへ渡す content block にそのまま残していたことでした。同じ形の報告は langchain-ai/langchain の Issue #33709 にもあり、with_fallbacks で Anthropic から別のモデルへ倒した瞬間に落ちる、という内容です。私のコードは LangChain を使っていませんでしたが、境界の扱いが同じだったのだと思います。

cache_control は Claude の手前で付ける飾りです

Anthropic の Messages API では、system や messages の content block に cache_control: {"type": "ephemeral"} を付けると、その位置までのプレフィックスがキャッシュされます。ブレークポイントは 1 リクエストに 4 か所まで置けます。用語集を system に載せて印を付けておくと、Opus 5.5 ではキャッシュ読み取りが $0.20 / Mtok まで下がるので、毎晩同じ用語集を何十回も送るバッチでは効き目が大きいのです。請求がどれだけ下がるかは構成しだいで、その勘所は キャッシュ読み取りが 75% 安くなっても、請求の下がり方は構成しだいで 10 倍違います に書いたとおりです。

一方で、このキーは Anthropic の API にしか意味がありません。他社の chat 形式へ content block を渡すとき、type と text 以外のキーが残っていれば、クライアント側の型検査で TypeError になるか、サーバー側で 400 として弾かれるか——どちらにしても届きません。

白状しますと、最初に疑ったのは相手側のクライアントの版でした。スタックトレースがメッセージ変換の関数を指していたので、ライブラリを上げれば直ると思い込み、上げても同じ行で落ちました。手が止まったのは、送る直前の payload を print してみたときです。用語集のブロックに、見慣れた "cache_control": {"type": "ephemeral"} がそのまま載っていました。

私が見落としていたのは、messages の配列を「会話の記録」として一つだけ持ち、Claude 向けにキャッシュ印を付けたその同じ配列を、フォールバック先へも渡していたことです。記録の形と、送信用の形を分けていなかったのです。

キャッシュ印は Claude の手前で付け、境界の向こうへは持ち出しません。 この一文を決めてから、コードは素直になりました。

境界で剥がす関数と、送る直前の検問

まず剥がす側です。元の配列を書き換えないこと、system にも同じ処理を通すこと。この二つだけ守れば短く済みます。

ANTHROPIC_ONLY_KEYS = {"cache_control"}
 
def strip_anthropic_extensions(messages, system):
    """他社モデルへ渡す直前に Claude 固有のキーを剥がす。元のオブジェクトは変更しない"""
    def clean(content):
        if isinstance(content, str):
            return content
        return [
            {k: v for k, v in block.items() if k not in ANTHROPIC_ONLY_KEYS}
            for block in content
        ]
    return (
        [{**m, "content": clean(m["content"])} for m in messages],
        clean(system),
    )

剥がすだけでは、将来 Claude 固有のキーを増やしたときに同じ事故を繰り返します。そこで送る直前に検問を置き、残っていれば送らずに止めるようにしました。

def assert_portable(obj, path="$"):
    """境界の向こうへ送る直前の検問。Claude 固有キーが残っていれば送る前に止める"""
    if isinstance(obj, dict):
        for k, v in obj.items():
            if k in ANTHROPIC_ONLY_KEYS:
                raise ValueError(f"{path}.{k} が残っています")
            assert_portable(v, f"{path}.{k}")
    elif isinstance(obj, list):
        for i, v in enumerate(obj):
            assert_portable(v, f"{path}[{i}]")

もう一つ、形の違いがあります。Anthropic では system がトップレベルの引数ですが、多くの chat 形式では role が system のメッセージとして先頭に置きます。content block の配列を文字列へ畳む処理も、ここでまとめて行います。

def to_chat_format(messages, system):
    """Anthropic の形(system は別引数・content は block 配列)を一般的な chat 形式へ"""
    def flatten(content):
        if isinstance(content, str):
            return content
        return "\n".join(b["text"] for b in content if b.get("type") == "text")
    out = [{"role": "system", "content": flatten(system)}]
    out += [{"role": m["role"], "content": flatten(m["content"])} for m in messages]
    return out

この三つの関数は、手元で cache_control 付きの配列を通して、検問が止めること・剥がしたあとに通ること・元の配列が変わっていないことを確かめてあります。text 以外の block(tool_use や tool_result、画像)は畳めませんので、ツールを使う会話はフォールバックの対象から外すという線引きにしました。壊れた形で届くより、Claude の回復を待って再実行するほうが、翌朝の私には扱いやすいのです。

429 では切り替えず、529 と接続エラーだけで切り替える

呼び出し側は次のようになりました。Claude を呼ぶ関数は、切り替えるべき失敗だけを ClaudeUnavailable に包み直します。

import os
import time
import anthropic
 
client = anthropic.Anthropic()  # ANTHROPIC_API_KEY=YOUR_API_KEY を環境変数で
MODEL = "claude-sonnet-5"
 
class ClaudeUnavailable(Exception):
    pass
 
def call_claude(messages, system):
    if os.environ.get("FORCE_FALLBACK") == "1":
        raise ClaudeUnavailable("FORCE_FALLBACK=1 により強制")
    try:
        r = client.messages.create(
            model=MODEL, max_tokens=1024, system=system, messages=messages
        )
    except (anthropic.InternalServerError, anthropic.APIConnectionError) as e:
        # 529 overloaded を含む 5xx と接続断だけを切り替え対象にする
        raise ClaudeUnavailable(str(e)) from e
    return r.content[0].text
 
def call_other(chat_messages):
    # 各自のクライアントに置き換える(OpenAI 互換の chat 形式を受け取る想定)
    ...
 
def ask(messages, system):
    for attempt in range(2):
        try:
            return call_claude(messages, system), MODEL
        except ClaudeUnavailable:
            if attempt == 0:
                time.sleep(30)  # 短い混雑ならこの間に Claude へ戻れる
    stripped_messages, stripped_system = strip_anthropic_extensions(messages, system)
    payload = to_chat_format(stripped_messages, stripped_system)
    assert_portable(payload)  # 残っていればここで止まる
    return call_other(payload), "fallback"

except に anthropic.RateLimitError を入れていないのは意図的です。429 は私の側の速度超過であって、先方の混雑ではありません。他社へ逃げても翌日には同じ壁に当たりますし、しばらく待てば通ります。Python SDK は既定で 2 回まで再試行してから例外を投げてくれますので、それでも届いた 5xx と接続断——これだけを「Claude が今は使えない」と読むことにしました。

切り替えの前に何秒待つかも、この機会に決めました。SDK の再試行は指数バックオフで数秒のうちに終わりますので、そのあとに私の側で 30 秒だけ間を置き、それでも 5xx なら他社へ出る、という順番です。夜のバッチは翌朝までに終わればよいので、数十秒の待ちは失うものがなく、短い混雑ならその間に Claude へ戻れるのです。

戻り値にどのモデルが答えたかを添えているのも、この夜の教訓です。フォールバック先の訳文は用語の癖が違いますので、朝の目視確認で「今日はどの行を疑うか」が分かるようにしておきたかったのです。Claude の中で Sonnet と Haiku を行き来する構成は Claude API の高可用性設計 — Sonnet / Haiku / Opus マルチモデルフォールバックを本番で成立させる に書きましたが、他社へ出る経路は、あちらより一段慎重に扱っています。

発火しないフォールバックは、試されていないのと同じです

半年間、一度も通ったことのない経路を「保険」と呼んでいたことが、いちばんの見落としでした。いまは FORCE_FALLBACK=1 を付けて、週に一度、バッチの先頭 1 件だけをフォールバック経路で通しています。費用は 1 件分で、ログには model=fallback の行が 1 本残ります。その 1 本が朝に見当たらなければ、保険のほうが壊れていると分かります。

要するに、剥がす関数、送る前の検問、そして週に一度の強制発火——この三つを揃えて、ようやくフォールバックが保険になったのだと感じています。

まずはご自身のフォールバック経路を、Claude が元気な日に一度だけ強制的に発火させてみていただければと思います。落ちるなら、混雑の夜ではなく、その日に落ちてくれるほうがはるかにありがたいのです。私はこの一件以来、境界を越えるデータには必ず検問を置くようにしています。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ API & SDK2026-08-23
Sonnet 5 の9月の値上げは行われません。7月に立てた見込みを公式の価格表から組み直す
Sonnet 5 の導入価格 2ドル/10ドルは、9月1日に3ドル/15ドルへ上がる予定でした。その引き上げは行われず、導入価格がそのまま標準価格になっています。手元の usage から実効コストを出し直す手順を、動くコードとともにまとめました。
⬡ API & SDK2026-09-17
画像は20枚ずつ送っています — 21枚目から全画像に別の上限がかかると知った日
素材フォルダの62枚を一度に送ったら invalid_request_error で落ちました。容量ではなく枚数が原因です。視覚トークンを28pxのパッチで数え直し、枚数・寸法・ペイロードの3つの上限を同時に見てバッチを組むまでの記録です。
⬡ API & SDK2026-09-06
訳文は自然なのに、実行時に落ちる翻訳がありました
多言語アプリの文言を Claude API で訳したとき、意味は正しいのに書式指定子だけが壊れることがあります。重大度で分けた受け入れ検査と、壊れた行だけを訳し直す修復ループを実装と実測でまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます