結論

Message Threads(メッセージスレッド)は、Claude の Messages API が会話を短い間サーバーに置いておき、2回目からは「新しいメッセージ」と「前回の応答の id」だけを送ればよくなる仕組みです。2026年11月2日にベータ(ヘッダー message-threads-2026-08-12)として公開されました。追加の料金は無く、トークンの数え方も変わりません。速くなるのは、長い会話で最初の文字が返るまでの時間です。

この記事でわかること

  • 作り方(create)・続け方(continue)・期限切れのときのやり直し(replay)を、公式ドキュメントで確かめた手順
  • 30ターンのエージェントで、送るバイト数が約15分の1になり、入力トークンは同じだった実測
  • 5つのエラーコードと SDK の例外クラス、使う・使わないの判断表

2026年11月5日更新|anthropic 2.5.0(Python 3.13)と 127.0.0.1 のモックで実行確認

この記事で実際に動かしたもの:Python 用の anthropic SDK 2.5.0 を作業用の venv に入れ、127.0.0.1 に立てた自作のモック(公式ドキュメントに書かれたスレッドの決まりをまねて応答するサーバー)にリクエストを送り、SDK が送った本文・ヘッダー・バイト数・再送の順番を記録しました。料金は公式の単価で自作の計算スクリプトを動かしています。本物の API には接続していないため、応答の速さとモデルの回答は測っていません。

flowchart TD
    A[1回目 thread: create<br>会話全体と system・tools] --> B[応答 id を受け取る]
    B --> C[2回目以降 thread: continue<br>新しいメッセージと前回の id だけ]
    C --> D{前回から5分以内?}
    D -->|はい| B
    D -->|いいえ 404| E[手元のコピーから<br>会話全体を create し直す]
    E --> B

よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
Claude Message Threads とは 会話をサーバーに短時間置き、毎回の送信を新しい分だけにする Messages API のベータ機能 Message Threadsとは
ベータヘッダー anthropic-beta: message-threads-2026-08-12 をスレッドのすべてのリクエストに付ける threadフィールドを自分で送る
Python での使い方 client.beta.messages.thread(...) で作り、send() を1ターンずつ呼ぶ SDKのスレッドヘルパー
料金は上がる?下がる? 変わらない。トークンは毎回会話全体ぶん数えられる。安くするのはプロンプトキャッシュ 料金とプロンプトキャッシュ
保存期間 最後のリクエストから5分で期限切れ。最後の使用から1時間以内に削除 期限切れとリプレイ
スレッドの削除・一覧 一覧・読み取り・削除の API は無い。期限で自動的に消える 使えない機能と制限
404 thread_not_found が出た 手元に残した会話全体で create し直す。SDK のヘルパーは1回だけ自動で行う よくあるエラー
使うべきか ツール結果が積み上がる長いエージェントには向く。短い一問一答は効果が小さい 使う・使わない判断表

確認環境:Ubuntu 24.04.5 LTS(2 CPU の共有マシン)の上で、Python 3.13.18 の venv に anthropic 2.5.0(PyPI、2026年11月4日公開)を入れました。SDK の接続先を base_url="http://127.0.0.1:18293" にして、自作のモック(Python 標準の http.server。公式ドキュメントの「作成時のみ・固定・自由」の決まり、5種類のエラーコードの形、期限切れの404をまねたもの。期限は試験のため5分ではなく3秒)に送り、送られた本文・ヘッダー・バイト数をログに記録しています。API キーはダミーで、環境変数は env -i で外しました。モックが返すエラーの文言(message)は自作で、本物の API の文言とは違います。エラーコード・例外クラス・SDK の再送の動きは本物の SDK のものです。仕組み・期限・料金・対応範囲は、Anthropic の公式ドキュメント(platform.claude.com/docs の Message threads 英語版・日本語版、Pricing、Prompt caching、Release notes)と公式ブログ(claude.com の「Make long-running agents faster with message threads」)で確かめました。速さの数字は Anthropic の公表値で、手元では測っていません。図は mermaid で描画を確かめています(確認日 2026年11月5日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Claude Message Threadsとは|送信量1/15の実測と使い方」

Claude Message Threadsとは(何が・いつ・誰に・いくらで)

この節では、Message Threads がどんな機能で、いつから、どこで、いくらで使えるのかが1枚の表で分かります。

Message Threads は、Messages API のリクエストに thread というパラメーターを1つ足すだけで使える、会話の「サーバー側の置き場所」です。公式ドキュメント(日本語版の題名は「メッセージスレッド」)は、仕組みを次のように説明しています。

完全な会話を、システムプロンプトとツールとともに最初に一度送信します。以降の各ターンでは、新しいメッセージと前回のレスポンスのidのみを送信します。APIはモデルを呼び出す前に会話を再構築します。

項目 内容(2026年11月5日時点)
何が Messages API(/v1/messages)の thread パラメーター。応答はふだんの Message と同じ形
いつ 2026年11月2日にパブリックベータ(公式ブログ・Release notes)
どこで Claude API、Claude Platform on AWS、Claude Models on Google Cloud(Anthropic managed)、Microsoft Foundry。いずれもベータ
誰に ツールの結果やファイルの編集が積み上がる長いエージェント、画面のスクリーンショットを送り続ける computer use、文書・画像の多い処理(公式ブログが挙げる用途)
いくらで 追加料金なし。トークンの単価とレート制限は変わらない
効果(公表値) Claude Code の大半の利用者で既定で有効になっており、50万〜100万トークンのリクエストで、最初のトークンまでの時間(TTFT)の中央値が約35%短くなった
保存 メモリ上にサーバー管理の鍵で暗号化して置き、ディスクには書かない。最後のリクエストから5分で期限切れ、最後の使用から1時間以内に削除
対応モデル Opus 5.5・Sonnet 5.5・Haiku 5.5・Opus 5・Sonnet 5 のほか、Opus 4.5〜4.8・Sonnet 4.5/4.6・Haiku 4.5 など(一覧は公式ドキュメントの supportedModels)

名前が似ている別の機能に注意します。公式ドキュメントは、Message Threads は Claude Managed Agents の「セッションスレッド」とは別物だと注記しています。また、claude.ai のチャット画面の「会話」とも関係はありません。この記事で扱うのは API の機能です。

従来の「毎回全部送る」方法との違い

この節では、Messages API が本来どう会話を扱っているのかと、スレッドで何が変わり、何が変わらないのかが分かります。

Messages API はもともと状態を持たない(ステートレスな)API です。2ターン目には1ターン目の質問と回答を、30ターン目には29ターンぶんの会話をすべて messages に入れて送り直します。会話が長くなるほどリクエストが大きくなり、API は受け取った会話をそのたびに検証し、トークンに分け直します。スレッドを使うと、その作業の大部分が「新しいターンの分」だけになります。

スレッドなし(毎回全部送る)

messages に会話全体を毎回入れる

system と tools も毎回送る

リクエストが会話の長さに比例して大きくなる

サーバーには何も残らない。期限切れも無い

スレッドあり(新しい分だけ送る)

1回目だけ会話全体と system・tools を送る

2回目からは新しいメッセージと previous_message_id

リクエストの大きさがほぼ一定になる

5分使わないと消えるので、作り直しの道を用意する

変わらないものが2つあります。1つはトークンの数で、公式ブログは「Claude は毎ターン会話全体を読むので、トークンの使用量は変わらない」と書いています。もう1つは会話の持ち主で、公式ドキュメントは「会話の信頼できる情報源(source of truth)は、引き続きアプリケーション側にあります」として、すべてのメッセージのコピーを自分で持つよう求めています。スレッドは「記録の保管庫」ではなく、送信を軽くするための一時的な置き場です。

Messages API の基本の呼び方は、関連記事「Claude API Python入門|Anthropic SDKコピペレシピ集」で扱っています。

使い方1:SDKのスレッドヘルパー(Python)

この節では、いちばん手早い使い方である SDK のスレッドヘルパーで、2ターンの会話を送る方法と、そのとき実際に送られた中身が分かります。

Python では client.beta.messages.thread(...) でヘルパーを作り、send() を1ターンずつ呼びます。ヘルパーは1ターン目でスレッドを作り、2ターン目からは続ける形で送り、ベータヘッダーも自分で付けます。会話全体はヘルパーの中(メモリ)に持ちます。公式ドキュメントの例とほぼ同じコードを、接続先だけモックに変えて動かしました。

import anthropic

client = anthropic.Anthropic(base_url="http://127.0.0.1:18293")  # 本番では base_url を外す

# ヘルパーを作る。この時点では何も送らない
thread = client.beta.messages.thread(
    model="claude-opus-5-5",
    max_tokens=1024,
    cache_control={"type": "ephemeral"},   # 毎ターンをキャッシュする(任意)
    system="You are a support agent for Acme. Keep replies short.",
)
print("before send:", thread.previous_message_id)

r1 = thread.send({"role": "user", "content": "Hi! I have a question about an order."})
print("turn1:", r1.id, r1.content[0].text)
r2 = thread.send({"role": "user", "content": "How long does standard shipping usually take?"})
print("turn2:", r2.id, r2.content[0].text)
print("previous_message_id:", thread.previous_message_id)
print("messages kept by helper:", len(thread.messages))
before send: None
turn1: msg_mock_001 ok (1 messages seen)
turn2: msg_mock_003 ok (3 messages seen)
previous_message_id: msg_mock_003
messages kept by helper: 4

モックは「サーバー側で組み立て直した会話の件数」を返すようにしてあります。2ターン目は1件しか送っていないのに、モックの中では3件(1ターン目の質問・回答・2ターン目の質問)として扱われました。ヘルパーの thread.messages には4件(2往復)が残っています。

実際に送られたリクエスト(モックのログ)

{"bytes": 253, "beta": "message-threads-2026-08-12", "version": "2026-10-21",
 "body": {"max_tokens": 1024,
          "messages": [{"role": "user", "content": "Hi! I have a question about an order."}],
          "model": "claude-opus-5-5", "cache_control": {"type": "ephemeral"},
          "system": "You are a support agent for Acme. Keep replies short.",
          "thread": {"type": "create"}}}
{"bytes": 235, "beta": "message-threads-2026-08-12", "version": "2026-10-21",
 "body": {"max_tokens": 1024,
          "messages": [{"role": "user", "content": "How long does standard shipping usually take?"}],
          "model": "claude-opus-5-5", "cache_control": {"type": "ephemeral"},
          "thread": {"type": "continue", "previous_message_id": "msg_mock_001"}}}

1ヘッダーは自動で付いた

2回とも anthropic-beta に message-threads-2026-08-12、anthropic-version に 2026-10-21 が入っていた。betas を書かなくてよい。

22回目は system が消えた

continue のリクエストには system が無く、model と cache_control は同じ値で送り直されていた。

3id は応答ごとに進む

2回目の previous_message_id は1回目の応答の id。次のターンは2回目の応答の id から続ける。

TypeScript では client.beta.messages.thread({...}) と await thread.send(...)、Go では client.Beta.Messages.NewThread(...)、Ruby では thread.send_message(...) と、各言語の SDK に同じヘルパーがあります(公式ドキュメントに C#・Go・Java・PHP・Ruby の例が載っています)。手元で確かめたところ、PyPI の anthropic は 2.2.0・2.3.0・2.4.0・2.5.0 の配布物に、スレッドのヘルパー(anthropic/lib/threads/)とベータヘッダーの定義が入っていました。

使い方2:threadフィールドを自分で送る(cURL・create)

この節では、ヘルパーを使わずに thread を自分で書く方法と、そのときに自分で守る決まりが分かります。

cURL や SDK の通常の create を使う場合は、スレッドのすべてのリクエストに message-threads-2026-08-12 のベータヘッダーを付けます。1回目は "thread": {"type": "create"}、2回目からは "thread": {"type": "continue", "previous_message_id": "<前回の id>"} を足し、system と tools は外します。次は公式ドキュメントの cURL の例です(手元では api.anthropic.com に送っていません)。

# 1回目:会話全体を送ってスレッドを作る
curl -sS --fail-with-body https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2026-10-21" \
  -H "anthropic-beta: message-threads-2026-08-12" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 1024,
    "cache_control": {"type": "ephemeral"},
    "system": "You are a support agent for Acme. Keep replies short.",
    "messages": [{"role": "user", "content": "Hi! I have a question about an order."}],
    "thread": {"type": "create"}
  }'

# 2回目:新しいメッセージだけ。message_id に1回目の応答の id を入れる。system と tools は送らない
curl -sS --fail-with-body https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2026-10-21" \
  -H "anthropic-beta: message-threads-2026-08-12" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 1024,
    "cache_control": {"type": "ephemeral"},
    "messages": [{"role": "user", "content": "How long does standard shipping usually take?"}],
    "thread": {"type": "continue", "previous_message_id": "'"$message_id"'"}
  }'

Python の通常の create で書く場合は、thread= と betas=["message-threads-2026-08-12"] を渡します。モックに送って確かめたコードです。

B = ["message-threads-2026-08-12"]
first = client.beta.messages.create(
    model="claude-opus-5-5", max_tokens=512, system="Be brief.",
    messages=[{"role": "user", "content": "hello"}],
    thread={"type": "create"}, betas=B)

second = client.beta.messages.create(
    model="claude-opus-5-5", max_tokens=512,            # system は書かない
    messages=[{"role": "user", "content": "next"}],
    thread={"type": "continue", "previous_message_id": first.id}, betas=B)

自分で書くときに忘れやすいのは3つです。毎回ヘッダーを付けること、continue では system と tools を外すこと、そして previous_message_id にいつも「最新の応答」の id を入れることです。各応答の id は1回しか続けられません。CLI(ant beta:messages create --beta message-threads-2026-08-12)でも同じ形で送れると公式ドキュメントにあります。

各リクエストで送るもの(作成時のみ・固定・自由)

この節では、continue のリクエストに何を入れ、何を入れてはいけないのかが、公式ドキュメントの3つの分類で分かります。

スレッドは、1回目の設定を「指紋(フィンガープリント)」として覚えていて、2回目以降のリクエストと照らし合わせます。公式ドキュメントはフィールドを次の3つに分けています。

分類 フィールド 決まり
作成時のみ system、tools create だけで送る。continue で送ると、null でも400エラー
固定 model、tool_choice、thinking、mcp_servers(認可トークンを除く)、context_management、output_config、container の skills、トップレベルの cache_control、anthropic-beta の値の組 毎回、create と同じ値で送る。違うと400の thread_fingerprint_mismatch
自由 max_tokens、stop_sequences、stream、metadata、service_tier など ターンごとに変えてよい

あわせて、MCP サーバーの URL・認可トークン・コンテナの id はスレッドに保存されないため、使うリクエストでは毎回送ります。system や tools を変えたいとき、固定のフィールドを変えたいときは、新しい値で会話全体を create し直します。

ヘルパーでの確かめ(update と fork)

ヘルパーの update() で自由のフィールドと system を変えたときの動きを確かめました。

update(system) -> AnthropicError : Cannot change `system` on a thread: the server keeps the ones the thread was created with. To change them, start a new thread from `thread.params`.
update(max_tokens) -> msg_mock_068
fork previous_message_id: None messages: 4
fork first send -> msg_mock_069
original send -> msg_mock_070
params keys: ['max_tokens', 'messages', 'model', 'system', 'unsupported_request_behavior']
{'type': 'create'} n=1 sys=True [200, 'msg_mock_067']
{'type': 'continue', 'previous_message_id': 'msg_mock_067'} n=1 sys=False [200, 'msg_mock_068']
{'type': 'create'} n=5 sys=True [200, 'msg_mock_069']        <- fork の1回目(会話全体)
{'type': 'continue', 'previous_message_id': 'msg_mock_068'} n=1 sys=False [200, 'msg_mock_070']
  • system の変更は、送る前に SDK が止めました。リクエストは1通も出ていません。メッセージは「thread.params から新しいスレッドを始める」よう案内しています。
  • max_tokens は変えても continue のまま送れました。自由のフィールドだからです。
  • fork() は会話をコピーし、最初の送信で会話全体(5件)を create しました。元のスレッドはそのまま continue で続きます。会話を枝分かれさせたいときの道具です。

送信量とトークン数の比較(30ターンの実測と計算)

この節では、ツールを30回呼ぶエージェントを想定して、毎回全部送る方法とスレッドで、送るバイト数とトークン数がどれだけ違うのかが分かります。

モックが「毎回 tool_use を返す」ようにして、約6KB のツール結果(ソースファイルの中身のつもり)を30回返すループを、2通りで動かしました。毎回全部送る方法は通常の client.messages.create、スレッドはヘルパーの send() です。

blob = ("def f(x):\n    return x * 2  # padding line for a source file\n" * 120)[:6000]

def result_for(reply):            # Claude の tool_use に、6KB のツール結果で答える
    tu = reply.content[0]
    return {"role": "user", "content": [{"type": "tool_result", "tool_use_id": tu.id, "content": blob}]}

# スレッドあり
th = client.beta.messages.thread(model="claude-opus-5-5", max_tokens=1024, system=SYSTEM, tools=TOOLS)
r = th.send({"role": "user", "content": "Fix the failing test in src/."})
for i in range(29):
    r = th.send(result_for(r))

# スレッドなし(会話を自分で積み上げて毎回全部送る)
conv = [{"role": "user", "content": "Fix the failing test in src/."}]
for i in range(30):
    r = client.messages.create(model="claude-opus-5-5", max_tokens=1024, system=SYSTEM, tools=TOOLS, messages=conv)
    conv += [{"role": "assistant", "content": [b.model_dump(exclude_none=True) for b in r.content]}, result_for(r)]
full   requests=30 1st=1,340B 2nd=7,747B 10th=59,003B 30th=187,143B total=2,827,245B input_tokens(mock,30th)=46,924 elapsed=125ms
thread requests=30 1st=1,367B 2nd=6,411B 10th=6,411B 30th=6,411B total=187,286B input_tokens(mock,30th)=46,924 elapsed=124ms
2.83MB毎回全部送る(30回の合計)30回目だけで187KB
0.19MBスレッド(30回の合計)2回目からは毎回6,411B で一定
15.1分の1送ったバイト数の比入力トークン(モックの概算)は同じ

送る量は約15分の1になり、ターンが進んでも増えませんでした。一方で、モックが会話全体から概算した入力トークン(1トークン=4バイトの目安)は、30回目で同じ46,924でした。本物の API でも、公式ブログが書くとおり、トークンは会話全体ぶん数えられます。

手元の 127.0.0.1 では、30回の所要時間は125ms と124ms でほぼ同じでした。同じマシンの中ではバイト数の差が時間に表れないためで、公表値の約35%の短縮は、ネットワークの転送と、サーバーでの検証・トークン化が減る分です。自分の環境でどれだけ速くなるかは、本物の API で stream=True にして最初のイベントまでの時間を測るのが確実です。

トークン数と金額の計算(Claude Opus 5.5)

トークンの数で同じ30ターンを計算しました。system と tools が2,000トークン、毎ターンの出力が200トークン、ツールの呼び出しと結果で1,550トークン増える、という置き方です。単価は公式の Pricing(2026年11月5日時点、Opus 5.5 は入力4ドル・5分キャッシュ書き込み5ドル・キャッシュ読み取り0.20ドル・出力20ドル/100万トークン)です。

input tokens per request: 1st=2030 10th=17780 30th=52780  total=822150
cache write=52780 read=769370
no cache : $3.4086  (thread or not: same)
cache 5m : $0.5378  (thread or not: same)
方法 30回の入力トークン 金額(30ターン) 送る量
毎回全部送る・キャッシュなし 822,150 約3.41ドル 多い
スレッド・キャッシュなし 822,150(同じ) 約3.41ドル(同じ) 少ない
毎回全部送る・キャッシュあり 書き込み52,780+読み取り769,370 約0.54ドル 多い
スレッド・キャッシュあり 書き込み52,780+読み取り769,370(同じ) 約0.54ドル(同じ) 少ない

金額を下げるのはプロンプトキャッシュで、スレッドではありません。スレッドは「送る量と待ち時間」、キャッシュは「金額」と、受け持ちが違います。トークンの数え方そのものは、関連記事「トークンとは|生成AIの数え方・日本語の文字数目安と節約方法」で扱っています。

料金とプロンプトキャッシュ(スレッドは料金を変えない)

この節では、スレッドとプロンプトキャッシュの関係と、キャッシュの区切り(ブレークポイント)を4つまでに収める決まりが分かります。

公式ドキュメントは「スレッドはプロンプトを自動的にキャッシュしません」と書いています。毎ターンをキャッシュするには、トップレベルの cache_control をスレッドのすべてのリクエストで送ります。API はそれを各リクエストの最後のキャッシュできるブロックに当て、前の会話をキャッシュから読み、新しいターンをキャッシュに書きます。トップレベルの cache_control は固定のフィールドなので、途中で付けたり外したりすると thread_fingerprint_mismatch になります。

  • 区切りは1リクエスト4つまで。トップレベルの cache_control も1つと数え、create の system・tools に付けた区切りは continue のたびに数え直します。保存済みの前のメッセージに付けた区切りは数えません。5つ目で400の thread_unsupported_request(unsupported: ["cache_control"])です。
  • 送った区切りは動かせない・外せない。continue では前のメッセージを送り直せないためです。
  • キャッシュが効くのは最小の長さを超えてから。Prompt caching のページでは、Opus 5.5・Sonnet 5.5・Haiku 5.5 の最小は512トークンです。
  • 期限はどちらも5分が基本。スレッドは最後のリクエストから5分、キャッシュも既定は5分です。キャッシュは "ttl": "1h" で1時間にできますが、スレッドの5分は延ばせません。

料金そのものは、公式の Pricing の単価がそのまま使われます。Opus 5.5・Sonnet 5.5 のキャッシュ読み取りは入力単価の0.05倍、Haiku 5.5 は0.1倍で、Haiku 5.5 はプロンプトが10万トークンを超えたリクエストの単価が上がります(いずれも2026年11月5日時点)。

期限切れとリプレイ(5分・1時間・404)

この節では、スレッドが5分で切れたときに何が起き、どう作り直すのかが、SDK の実際の動きで分かります。

スレッドはベストエフォート(できる範囲での保持)です。最後のリクエストから5分で期限切れになり、API が応答を保存するたびに5分は数え直されます。続けられなくなった continue は404(error_code が thread_not_found)になり、手元の会話全体で create し直します。これを公式ドキュメントは「リプレイ」と呼んでいます。

モックの期限を3秒にして、ヘルパーで2ターン送り、4秒待ってから3ターン目を送りました。

th = client.beta.messages.thread(model="claude-sonnet-5-5", max_tokens=512, system="Be brief.")
th.send({"role": "user", "content": "turn 1"})
th.send({"role": "user", "content": "turn 2"})
time.sleep(4)                                   # モックの期限(3秒)を過ぎる
r = th.send({"role": "user", "content": "turn 3"})
sleep 4s (mock TTL = 3s)
turn3 reply: msg_mock_063 ok (5 messages seen) | helper messages: 6
{'type': 'create'} messages=1 system=True -> [200, 'msg_mock_061']
{'type': 'continue', 'previous_message_id': 'msg_mock_061'} messages=1 system=False -> [200, 'msg_mock_062']
{'type': 'continue', 'previous_message_id': 'msg_mock_062'} messages=1 system=False -> [404, 'thread_not_found']
{'type': 'create'} messages=5 system=True -> [200, 'msg_mock_063']

404を受けたヘルパーは、例外を出さずに会話全体(5件)と system で create し直し、3ターン目の答えを返しました。呼び出す側のコードは何も変えていません。ただし、これはヘルパーがメモリに会話を持っているからできることで、プロセスが終わると会話も消えます。自分で thread を書く場合は、同じことを自分で組みます。

1

会話のコピーを自分で持つ

送ったメッセージと受け取った応答を、毎ターン手元(メモリ・DB)に追記する。スレッドに頼って会話を捨てない。

source of truth

2

404 の error_code を確かめる

存在しないモデル名でも404になるため、error.details.error_code が thread_not_found のときだけリプレイに進む。

NotFoundError

3

会話全体で create し直す

system・tools も付ける。前のメッセージに自分で付けた cache_control は外し、最後のブロックに1つ残して4つ以内にする。

replay

4

新しい応答の id から続ける

リプレイ直後の continue がまた404なら、その会話は保存できない大きさの可能性がある。thread なしで送る。

continue_unthreaded

404 の理由は応答に書かれません。公式ドキュメントが挙げる原因は、期限切れのほか、前のターンが保存されなかった(max_tokens で途中で切れたツール呼び出しで終わった応答など)、フォールバックのモデルが前の応答を書いた、前の応答がまだ生成中、別の組織・ワークスペース・ユーザー・サービスアカウントからのリクエスト、画像の無かった会話に初めて画像を足した、などです。スレッドのすべてのリクエストを同じ API キーとワークスペースで送ると、持ち主の違いによる404を避けられます。

ストリーミング・拒否・フォールバックでの注意

この節では、ストリーミング・拒否(refusal)・サーバー側のフォールバックとスレッドを組み合わせたときの決まりが分かります。

ストリーミングでは、応答の id は最初の message_start で届きますが、そこから続けるのは message_stop を受け取ってからです。早く送った continue は404になることがあります。ストリームが途中で切れたら同じ continue をもう一度送り、それが thread_already_continued か thread_continue_in_flight になったら、会話全体でリプレイします。公式ドキュメントは「リプレイは常に安全です」と書いています。ヘルパーは切れたストリームを自動では送り直さないため、送り直すときは send() をメッセージ無しで呼びます。Python のヘルパーで途中まで読んでやめるときは、with ブロックを抜けてストリームを閉じます。

  • 拒否のあと:stop_reason: "refusal" の応答からは続けません。API が拒否されたターンを持っている可能性があり、continue では消せないためです。そのターンを外すか言い換えて、新しいスレッドを作ります。ヘルパーはこれを自動で行います。
  • フォールバック:fallbacks はスレッドの全リクエストで送るか、どれにも送らないかを create のときに決めます。途中で足したり外したりすると thread_fingerprint_mismatch です。フォールバックのモデルが書いた応答(usage.iterations に fallback_message がある)は保存されないので、その id からは続けられず、会話全体で create し直します。
  • pause_turn:stop_reason: "pause_turn" で止まった応答は、その id から "messages": [] の continue で再開します。止まっている間に新しい user メッセージを送ると拒否されます。
  • 要約(コンパクション):スレッドは追記だけなので、前のターンを要約に置き換えられません。要約を自分のコードで作り、新しい create の普通のメッセージとして送ります。

よくあるエラー/つまずき(原文つき)

この節では、スレッドで出るエラーコードと、Python SDK で受け取ったときの例外クラス、直し方が分かります。

公式ドキュメントによると、次の表のエラーはモデルが動く前に返り、トークンは使われず、スレッドとその期限も変わりません。例外クラスは anthropic 2.5.0 の anthropic.types にあるもので、モックで各エラーを起こして確かめました。

状態 error_code Python の例外 原因 直し方
404 thread_not_found ThreadNotFoundError 期限切れ・未保存・別のキーなど。理由は返らない 会話全体で create し直す
400 thread_fingerprint_mismatch ThreadFingerprintMismatchError model など固定のフィールドが create と違う 同じ値で送る。変えるなら新しいスレッド
400 thread_already_continued ThreadAlreadyContinuedError 同じ id から2回続けた 最新の応答の id から続ける
400 thread_continue_in_flight ThreadContinueInFlightError 同じ id からの continue がまだ処理中 終わるのを待つか、リプレイする
400 thread_unsupported_request ThreadUnsupportedRequestError スレッドが対応しない機能・組織の設定 その機能を外すか、thread なしで送る
400 (無し) BadRequestError continue に system・tools を入れた、previous_message_id が無い continue から system・tools を外す

モックで起こしたときの出力です(message の文言はモックの自作で、本物の API の文言ではありません)。

create -> msg_mock_064
[model changed] ThreadFingerprintMismatchError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'thread: fields differ from the thread: model', 'details': {'error_code': 'thread_fingerprint_mismatch'}}, 'request_id': 'req_mock'}
[system on continue] BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'thread: `system` and `tools` may only be set when creating a thread'}, 'request_id': 'req_mock'}
[correct continue] 200 msg_mock_065
[same id again] ThreadAlreadyContinuedError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'thread: msg_mock_064 has already been continued', 'details': {'error_code': 'thread_already_continued'}}, 'request_id': 'req_mock'}
[unknown id] ThreadNotFoundError: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'thread: cannot continue from msg_does_not_exist', 'details': {'error_code': 'thread_not_found'}}, 'request_id': 'req_mock'}

公式ドキュメントに載っている、本物の API のエラー本文の例は次のとおりです(区切りが5つあったときのもの)。

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "thread: a maximum of 4 blocks with cache_control may be provided when `thread` is set. Found 5.",
    "details": {
      "error_code": "thread_unsupported_request",
      "unsupported": ["cache_control"]
    }
  },
  "request_id": "req_011CSHoEeqs5C35K2UUqR7Fy"
}

例外を受けたときの組み方の例です。ThreadNotFoundError は NotFoundError の、他の4つは BadRequestError の子クラスなので、既存の例外処理にも引っかかります。

from anthropic.types import ThreadNotFoundError, ThreadAlreadyContinuedError, ThreadContinueInFlightError

try:
    r = client.beta.messages.create(..., thread={"type": "continue", "previous_message_id": last_id}, betas=B)
except (ThreadNotFoundError, ThreadAlreadyContinuedError, ThreadContinueInFlightError):
    r = client.beta.messages.create(..., system=SYSTEM, tools=TOOLS,
                                    messages=my_copy_of_conversation,   # 手元のコピー
                                    thread={"type": "create"}, betas=B)
last_id = r.id

対応しない設定のとき:unsupported_request_behavior

組織の設定(HIPAA 対応など)でスレッドが使えないとき、ヘルパーの既定("error")では例外になります。unsupported_request_behavior="continue_unthreaded" にすると、thread なしの普通のリクエストで送り直します。モックに unsupported: ["hipaa"] を返させて確かめました。

error -> ThreadUnsupportedRequestError {'error_code': 'thread_unsupported_request', 'unsupported': ['hipaa']}
   thread={'type': 'create'} n=1 -> [400, 'thread_unsupported_request']
continue_unthreaded -> ok msg_mock_004
   thread={'type': 'create'} n=1 -> [400, 'thread_unsupported_request']
   thread=None n=1 -> [200, 'msg_mock_003']
   thread=None n=3 -> [200, 'msg_mock_004']

continue_unthreaded では、2ターン目からは会話全体(3件)を thread なしで送っていました。スレッドが使えない環境でも同じコードで動かせる反面、送る量は従来どおりに戻ります。API 一般の429・529の扱いは、関連記事「LLM APIの429・529エラー対処|リトライ実装とエラー一覧」で扱っています。

continue で送るメッセージ

やってはいけない会話全体や前回の応答を messages に入れて continue する。API は照らし合わせないので、同じメッセージが2回入るかエラーになる
→
代わりに前回の応答のあとに増えた分(次の user メッセージか tool_result)だけを送る

見分ける問い:「このメッセージは前回の応答より後に生まれたか」

会話の保存

やってはいけないスレッドに会話を任せて、手元のコピーを捨てる
→
代わりに毎ターン手元に追記し、404のときにそこから create し直す

見分ける問い:「5分止まったあと、会話を作り直せるか」

スレッドが使えない機能と制限(削除APIは無い)

この節では、スレッドと一緒に使えない機能と、スレッドそのものに対してできないことが分かります。

スレッドを一覧・読み取り・削除する API はありません。消したいときは、使うのをやめれば5分で期限切れになり、最後の使用から1時間以内に削除されます。そのほか、公式ドキュメントが挙げる「スレッドが対応しない機能」は次のとおりです。

機能・設定 thread を付けたときの結果 代わりの方法
Message Batches バッチの結果で invalid_request_error バッチでは thread を付けずに送る
トークンカウント(count_tokens) 400エラー system・tools を含む会話全体を数える
コンパクション(compaction) 400エラー 要約を自分で作り、新しい create で送る
コンテキスト編集 clear_thinking(keep: all)以外は不可 使うなら thread なし
HIPAA 対応の組織 thread_unsupported_request(hipaa) thread なしで送る
Inference hooks を有効にした組織 thread_unsupported_request(inference_hooks) thread なしで送る

このほか、Opus 5.5・Sonnet 5.5・Haiku 5.5 などでは、system や tools を変えて create し直すと、会話に残っている thinking・redacted_thinking のブロックが無効になり、400エラーになることがあります。避けるには、送り直す会話からそれらのブロックを外します(前の推論は使われなくなります)。error.details.unsupported に知らない値が来たときは、thread なしで会話全体を送るよう公式ドキュメントは案内しています。

データ保持とセキュリティ(ZDR対象外)

この節では、スレッドの中身がどこに・どれだけの間置かれるのかと、ゼロデータ保持(ZDR)との関係が分かります。

Message Threads は ZDR(Zero Data Retention)の対象ではありません。HIPAA の対象でもありません。公式ドキュメントによると、スレッドの中身(会話・システムプロンプト・ツール)はメモリ上にサーバー管理の鍵で暗号化して置かれ、永続ストレージには書かれず、最後の使用から1時間以内に削除されます。スレッドを使っても、他の ZDR 対象の機能の扱いは変わりません。

  • スレッドが持つもの:モデルに渡す会話、create の system と tools、固定のフィールドの指紋。
  • ファイルは参照で持つ:会話で使うファイル(Files API など)は、スレッドを使っている間は消さずに置いておきます。
  • 持ち主の範囲:作った組織・ワークスペース・ユーザー(サービスアカウント)以外からは続けられません。Claude Platform on AWS では、作った IAM ロールか IAM ユーザーの認証情報で署名します。

社内の決まりで ZDR を条件にしている場合は、スレッドを使う前に、その条件にこの機能が含まれるかを確かめます。

Message Threadsを使う・使わない判断表

この節では、自分の用途でスレッドを使うべきかどうかを、場面ごとに判断できます。

効くのは「1つの会話で何十回もリクエストを送り、会話が数十万トークンまで伸びる」場面です。公式ブログが TTFT の短縮を示したのも50万〜100万トークンのリクエストでした。短い一問一答や、5分以上あいてから続く会話では、毎回リプレイになり効果は小さくなります。

場面 判断 理由
コーディングエージェント(ツールを何十回も呼ぶ) 使う ツール結果が積み上がり、毎回の送信が大きくなるため
computer use(スクリーンショットを送り続ける) 使う 画像を毎回送り直さずに済む
長い文書・画像を最初に渡して質問を重ねる 使う 最初の大きな入力を2回目から送らずに済む
チャットボット(人の返事を待つ) 条件つき 返事が5分以上あくと毎回リプレイになる
1回で終わる分類・要約・抽出 使わない 2回目が無いので効果が無い
Message Batches・count_tokens 使えない スレッドが対応していない
ZDR・HIPAA が条件の組織 使わない 対象外、または thread_unsupported_request になる

導入するときの確かめ項目です。

  • 1会話のコピーを手元に持っている404 のときに create し直せる。ヘルパーでも、プロセスをまたぐなら自分で保存する
  • 2固定のフィールドを途中で変えないmodel・thinking・output_config・cache_control・betas の組。変えるなら新しいスレッド
  • 3キャッシュの区切りは4つ以内トップレベルの cache_control と、system・tools の区切りも数える
  • 4同じ API キーとワークスペースで送る別のキーからの continue は thread_not_found になる
  • 5ストリームは message_stop まで読むid は message_start で届くが、続けるのは最後まで受け取ってから
  • 6効果を本物の API で測るstream=True で最初のイベントまでの時間を、スレッドあり・なしで比べる

エージェントの組み方そのものは関連記事「AIエージェントとは|仕組みとワークフローとの違い・作り方入門」で、Claude Code で文脈を小さく保つ方法は関連記事「Claude Codeサブエージェントの使い方|実測で見る委任とコンテキスト節約」で扱っています。

よくある質問

この節では、Message Threads について調べる人が気にすることへの短い答えが分かります。

Q. Message Threads を使うと料金は安くなりますか? A. 安くなりません。公式ドキュメントは「スレッドは料金やレート制限を変更しません」と書いており、トークンは毎回会話全体ぶん数えられます。金額を下げたいときは、トップレベルの cache_control でプロンプトキャッシュを使います。

Q. スレッドはどれくらいの間残りますか? A. 最後のリクエストから5分で期限切れになり、応答が保存されるたびに5分は数え直されます。中身は最後の使用から1時間以内に削除されます。期限を延ばす設定はありません。

Q. スレッドを削除する方法はありますか? A. 一覧・読み取り・削除の API はありません。使うのをやめれば期限で消えます。

Q. ベータヘッダーを付け忘れるとどうなりますか? A. 公式ドキュメントは、スレッドのすべてのリクエストに message-threads-2026-08-12 を付けるよう求めています。SDK のヘルパーを使えば自動で付きます(手元のログで2回とも付いていることを確かめました)。anthropic-beta の値の組は固定のフィールドなので、途中で別のベータを足すと thread_fingerprint_mismatch になります。

Q. 会話の途中でシステムプロンプトを変えられますか? A. 同じスレッドでは変えられません。SDK のヘルパーでは送る前に AnthropicError で止まりました。thread.params から新しい system で新しいスレッドを作ります。

Q. 会話を枝分かれさせたいです。 A. 1つの応答の id から続けられるのは1回だけです。ヘルパーの fork() で会話をコピーすると、最初の送信で会話全体を create し、その後は元のスレッドと別々に続きます。

Q. ストリーミングでも使えますか? A. 使えます。id は message_start で届きますが、続けるのは message_stop を受け取ってからです。Python のヘルパーでは thread.stream(...) を使います。

Q. Amazon Bedrock や Google Cloud でも使えますか? A. 公式ドキュメントの対応先は、Claude API、Claude Platform on AWS、Claude Models on Google Cloud(Anthropic managed)、Microsoft Foundry です(2026年11月5日時点)。名前の近い別の提供形態が対応しているかは、各提供先のドキュメントで確かめます。

Q. Claude Code では何か設定が要りますか? A. 公式ブログは、Claude Code の大半の利用者で既定で有効になっていると書いています。この記事は API での使い方を扱っており、Claude Code での切り替え方法は確かめていません。

Q. count_tokens でスレッドのトークン数を数えられますか? A. 数えられません。thread を付けた count_tokens は400エラーになります。system・tools を含む会話全体を、thread なしで数えます。

Q. 「スレッド」はプログラムのスレッド(並行処理)と関係がありますか? A. ありません。ここでの thread は「ひと続きの会話」という意味で、Python の threading のような並行処理の仕組みとは別の言葉です。

まとめ

  • Message Threads は、Messages API の thread パラメーターで会話をサーバーに短時間置き、2回目からは新しいメッセージと前回の応答の id だけを送る仕組み。2026年11月2日にベータ公開、ヘッダーは message-threads-2026-08-12。
  • Python は client.beta.messages.thread() と send() が最短。手元のログでは、ヘッダーが自動で付き、2回目の continue から system が外れていた。
  • 30ターンのツールループでは、送ったバイト数が2.83MB から0.19MB(約15分の1)になり、入力トークンは同じだった。料金は変わらず、金額を下げるのはプロンプトキャッシュ。
  • 5分で期限切れ、1時間以内に削除。404 の thread_not_found は会話全体で create し直す。ヘルパーは1回だけ自動で行い、手元でも例外を出さずに続いた。会話のコピーは自分で持つ。
  • 固定のフィールド・4つまでのキャッシュ区切り・Batches や count_tokens に使えないこと・ZDR 対象外を確かめてから、長いエージェントから導入する。

参考・出典

確認日はいずれも 2026年11月5日です。

  • Claude Platform Docs「Message threads」(英語版):https://platform.claude.com/docs/en/build-with-claude/message-threads
  • Claude Platform Docs「メッセージスレッド」(日本語版):https://platform.claude.com/docs/ja/build-with-claude/message-threads
  • Claude Blog「Make long-running agents faster with message threads」(2026年11月2日):https://claude.com/blog/message-threads
  • Claude Platform Docs「Release notes」:https://platform.claude.com/docs/en/release-notes/overview
  • Claude Platform Docs「Pricing」:https://platform.claude.com/docs/en/about-claude/pricing
  • Claude Platform Docs「Prompt caching」:https://platform.claude.com/docs/en/build-with-claude/prompt-caching
  • PyPI「anthropic」2.5.0:https://pypi.org/project/anthropic/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Claude Message Threadsとは|送信量1/15の実測と使い方」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

成長のヒミツ、 ぜんぶ丸わかり