CLAUDE LABEN
FORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられていますFORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられています
記事一覧/API & SDK
API & SDK/2026-04-26中級

Claude APIストリーミングが途中で切れる — 症状別の診断と修正パターン

Claude APIのストリーミングレスポンスが途中で止まる原因をタイプ別に診断し、タイムアウト・レート制限・max_tokens・クライアント実装バグそれぞれの修正コードを解説します。

streaming16troubleshooting61api38rate-limit7timeout8python22

ストリーミングを実装したのに、Claudeの返答がある行で突然止まる。エラーが返ってくれば手がかりになりますが、無音で停止するケースの方が厄介です。「接続は生きているのに、テキストが来ない」という状態になったことはないでしょうか。

この止まり方にはいくつかのタイプがあり、タイプを特定しないまま「とりあえずリトライ」で対応しても、根本解決にはなりません。ここでは症状から原因を絞り込む診断フローと、コードレベルの修正パターンをまとめます。

まず「エラーか無音か」でタイプを分ける

最初の判断ポイントは、例外が発生したかどうかです。

エラーが発生した場合 — エラーコードを確認する

429 Too Many Requests       → レート制限(原因2)
529 Overloaded              → サーバー過負荷(原因2と同じ対処)
400 Request too large       → コンテキストウィンドウ超過(原因3)
ReadTimeout / TimeoutError  → ネットワークタイムアウト(原因1)
ECONNRESET                  → 接続リセット(原因1)

エラーなしで止まった場合 — stop_reason を確認する

stop_reason: "max_tokens"   → max_tokens設定の問題(原因4)
stop_reason: "end_turn"     → 正常終了(期待どおり)
イベント自体が来なくなった   → クライアント実装の問題(原因5)

stop_reasonはストリームの最後のイベントに含まれています。確認していなかった場合は、まずここをロギングするだけで原因が分かることがほとんどです。

原因1: ネットワークタイムアウト(最も多い)

長い応答を生成中に、HTTP接続がタイムアウトで切れるケースです。requestshttpxのデフォルトタイムアウトは30〜60秒に設定されていることが多く、Claudeが長文を生成する最中に静かに切れてしまいます。

import anthropic
import httpx
 
# デフォルト設定では長い応答が途切れることがある
client_default = anthropic.Anthropic()
 
# ストリーミング用にタイムアウトを調整する
client = anthropic.Anthropic(
    timeout=httpx.Timeout(
        connect=10.0,   # 接続確立
        read=300.0,     # データ受信(長い生成に対応)
        write=10.0,     # 送信
        pool=10.0,      # コネクションプール
    )
)
 
with client.messages.stream(
    model="claude-sonnet-4-6",
    max_tokens=4096,
    messages=[{"role": "user", "content": "詳細なレポートを作成してください..."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

read=300.0(5分)は多く見えるかもしれませんが、claude-sonnet-4-6が複雑なタスクを処理する場合、2〜3分かかることがあります。私の本番環境ではread=300を標準設定にしていますが、それ以来タイムアウト起因の切断はほぼなくなりました。

原因2: レート制限と過負荷(429 / 529)

429 RateLimitErrorはリクエスト数またはトークン数の上限超過、529 OverloadedErrorはサーバー側の一時的な過負荷です。どちらも時間を置けば回復するため、指数バックオフ付きのリトライで対応します。

import anthropic
import time
 
def stream_with_retry(client, max_retries=3, **kwargs):
    """指数バックオフ付きストリーミング"""
    for attempt in range(max_retries):
        try:
            with client.messages.stream(**kwargs) as stream:
                collected = ""
                for text in stream.text_stream:
                    print(text, end="", flush=True)
                    collected += text
                return collected
        except anthropic.RateLimitError:
            if attempt == max_retries - 1:
                raise
            wait = (2 ** attempt) * 30  # 30秒 → 60秒 → 120秒
            print(f"\n⚠️ レート制限 — {wait}秒待機 ({attempt+1}/{max_retries})")
            time.sleep(wait)
        except anthropic.APIStatusError as e:
            if e.status_code == 529 and attempt < max_retries - 1:
                wait = (2 ** attempt) * 10
                print(f"\n⚠️ 過負荷 — {wait}秒待機 ({attempt+1}/{max_retries})")
                time.sleep(wait)
            else:
                raise
 
client = anthropic.Anthropic()
stream_with_retry(
    client,
    model="claude-sonnet-4-6",
    max_tokens=2048,
    messages=[{"role": "user", "content": "こんにちは"}],
)

レート制限が頻繁に発生する場合は、Anthropicのコンソールでティアを確認してください。ティア1では1分あたりのトークン数制限が比較的低く設定されています。詳細はClaude API 429エラーの対処法をご覧ください。

原因3: コンテキストウィンドウ超過(400エラー)

入力トークン数がモデルの上限を超えると、400 Bad Requestが返されます。これはストリーミング開始前に発生するため、「ストリームが始まらない」という症状になります。

max_tokensが大きすぎる(入力トークン数 + max_tokens がウィンドウ上限を超える)場合も同様です。

import anthropic
 
client = anthropic.Anthropic()
 
def get_safe_max_tokens(messages, model="claude-sonnet-4-6", buffer=100):
    """入力に合わせて安全なmax_tokensを計算する"""
    count_response = client.messages.count_tokens(
        model=model,
        messages=messages,
    )
    input_tokens = count_response.input_tokens
    
    # claude-sonnet-4-6のコンテキストウィンドウは200,000トークン
    # 出力は最大64,000トークン
    context_limit = 200000
    output_limit = 64000
    
    max_available = context_limit - input_tokens - buffer
    safe_max = min(max_available, output_limit)
    
    print(f"入力: {input_tokens}トークン / 利用可能出力: {safe_max}トークン")
    return max(safe_max, 1)
 
messages = [{"role": "user", "content": "非常に長い文書..."}]
safe_tokens = get_safe_max_tokens(messages)
 
with client.messages.stream(
    model="claude-sonnet-4-6",
    max_tokens=safe_tokens,
    messages=messages,
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

長い会話履歴を全て渡す設計の場合、10往復を超えた辺りでこの問題が起きやすくなります。古いメッセージを要約して圧縮するか、重要な部分だけを抽出する設計を検討してください。

原因4: max_tokensに達した(正常終了との区別)

これは「エラー」ではなく「正常終了」ですが、生成途中で止まったように見えるため混乱しやすいです。stop_reasonを確認することで判別できます。

import anthropic
 
client = anthropic.Anthropic()
 
with client.messages.stream(
    model="claude-sonnet-4-6",
    max_tokens=256,  # 短すぎると途中で止まる
    messages=[{"role": "user", "content": "Pythonの非同期処理について詳しく説明してください"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    
    # ストリーム終了後にstop_reasonを確認
    final_message = stream.get_final_message()
    print(f"\n\n--- stop_reason: {final_message.stop_reason} ---")
    
    if final_message.stop_reason == "max_tokens":
        print("⚠️ max_tokensに達しました。max_tokensを増やしてください。")
    elif final_message.stop_reason == "end_turn":
        print("✅ 正常に完了しました。")

max_tokens=256のような低い値のままで詳細な説明を求めると、必ず途中で切れます。用途に応じて適切な値に設定してください。claude-sonnet-4-6は最大64,000トークンの出力をサポートしています。

原因5: クライアント実装のバグ

エラーもなく、stop_reasonend_turnなのにテキストが途切れているように見える場合は、クライアント側の実装に問題がある可能性があります。よくあるパターンを確認します。

非同期コードでのawait漏れ

import asyncio
import anthropic
 
# 同期クライアントを非同期コードで使う(間違い)
async def wrong_approach():
    client = anthropic.Anthropic()  # 非同期には AsyncAnthropic を使う
    # ...
 
# 正しい実装
async def correct_approach():
    client = anthropic.AsyncAnthropic()
    
    async with client.messages.stream(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=[{"role": "user", "content": "こんにちは"}],
    ) as stream:
        async for text in stream.text_stream:
            print(text, end="", flush=True)
    
    print()  # 改行
 
asyncio.run(correct_approach())

ストリームを途中でクローズしてしまう

# 問題のある実装
with client.messages.stream(...) as stream:
    for text in stream.text_stream:
        if some_condition:
            break  # ここでストリームが切断される
        process(text)
 
# 全イベントを受け取ってから処理する場合はcollect_streamを使う
message = client.messages.stream(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "こんにちは"}],
).get_final_message()
print(message.content[0].text)

本番で使えるストリーミング実装

上記5つの原因に対応した、本番向けの実装例です。

import anthropic
import httpx
import time
from collections.abc import Iterator
 
def create_robust_client() -> anthropic.Anthropic:
    """本番用クライアントの初期化"""
    return anthropic.Anthropic(
        timeout=httpx.Timeout(connect=10.0, read=300.0, write=10.0, pool=10.0)
    )
 
def stream_with_diagnostics(
    client: anthropic.Anthropic,
    messages: list,
    model: str = "claude-sonnet-4-6",
    max_tokens: int = 4096,
    max_retries: int = 3,
) -> Iterator[str]:
    """
    診断機能付きストリーミング。
    タイムアウト・レート制限・過負荷を自動リトライ。
    stop_reasonをロギングして問題を可視化する。
    """
    for attempt in range(max_retries):
        try:
            with client.messages.stream(
                model=model,
                max_tokens=max_tokens,
                messages=messages,
            ) as stream:
                for text in stream.text_stream:
                    yield text
                
                final = stream.get_final_message()
                if final.stop_reason == "max_tokens":
                    print(f"\n⚠️ max_tokens({max_tokens})に到達。必要なら増やしてください。")
                # stop_reason: "end_turn" = 正常完了
                return
        
        except anthropic.RateLimitError:
            wait = 2 ** attempt * 30
            if attempt < max_retries - 1:
                print(f"\n⚠️ レート制限 — {wait}s待機 ({attempt+1}/{max_retries})")
                time.sleep(wait)
            else:
                raise
        
        except anthropic.APIStatusError as e:
            if e.status_code == 529 and attempt < max_retries - 1:
                wait = 2 ** attempt * 10
                print(f"\n⚠️ 過負荷 — {wait}s待機 ({attempt+1}/{max_retries})")
                time.sleep(wait)
            else:
                raise
        
        except (httpx.ReadTimeout, httpx.ConnectError) as e:
            if attempt < max_retries - 1:
                print(f"\n⚠️ ネットワークエラー: {e} — 再試行 ({attempt+1}/{max_retries})")
                time.sleep(5)
            else:
                raise
 
# 使用例
client = create_robust_client()
for text in stream_with_diagnostics(
    client,
    messages=[{"role": "user", "content": "Pythonの非同期処理について詳しく説明してください"}],
):
    print(text, end="", flush=True)

stop_reasonのロギングを追加するだけでも、「なぜ止まったのか」が分かるケースがかなり多いです。エラーハンドリングよりまず先に、stop_reasonの可視化から始めることをおすすめします。

APIエラー全般についてはClaude APIエラー完全ガイドもあわせてご参照ください。


ストリーミングが止まる原因を絞り込む手順は、エラーコード確認 → stop_reason確認 → クライアントログ確認の順です。次のステップとして、既存のストリーミングコードにfinal_message.stop_reasonのログ出力を1行追加してみてください。原因不明だった切断のうち、半数以上はmax_tokens到達だったと分かることがよくあります。

シェア

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

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

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

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

関連記事

API & SDK2026-04-07
Claude API の429・503エラーとタイムアウトを完全解決する対処法
Claude APIで発生する429レート制限・503サービス利用不可・タイムアウトエラーの原因と解決策を徹底解説。指数バックオフ実装からTier別上限の確認方法まで、現場で使える対処法を網羅します。
API & SDK2026-06-27
Claude API のストリーミングが「エラーも出さずに」止まるとき — 沈黙する停止を検知して途中から続ける運用メモ
Claude API のストリーミングが例外も出さずに無言で止まる「沈黙停止」を、トークン間隔のウォッチドッグで検知し、受信済みテキストを引き継いで途中から再開する実装と、長時間の自動運用で崩れないためのタイムアウト予算の設計をまとめます。
API & SDK2026-05-16
Claude API の Tool Use でスキーマ定義ミスが出たとき:実際に遭遇した3つのパターンと診断手順
Claude API の Tool Use でよく起きるスキーマ定義ミス・invalid_tool_use・Claude がツールを呼ばない問題を、実際のアプリ開発で遭遇したケースをもとに診断手順と修正パターンで解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →