CLAUDE LABEN
CODE — Claude Codeに大型の品質・信頼性アップデート。/rewindでの巻き戻し、MCPレジリエンス向上、OAuthハンドリングの安定化が入りましたCODE — ストリーミングと長時間セッション中のCPU・メモリ使用量が削減され、長く回す自動運用が安定しますADMIN — 組織向けモデル制限が追加され、管理者が利用可能なモデルを制御できるようになりましたMCP — 構造化出力・リモートMCP・セッション再開(resume)の信頼性が向上しましたMODEL — Claude Fable 5が一般提供。100万トークン文脈・常時アダプティブ思考・128K出力が特徴ですLINEUP — 主力はOpus 4.8・Sonnet 4.6・Haiku 4.5。用途に応じて使い分けられますCODE — Claude Codeに大型の品質・信頼性アップデート。/rewindでの巻き戻し、MCPレジリエンス向上、OAuthハンドリングの安定化が入りましたCODE — ストリーミングと長時間セッション中のCPU・メモリ使用量が削減され、長く回す自動運用が安定しますADMIN — 組織向けモデル制限が追加され、管理者が利用可能なモデルを制御できるようになりましたMCP — 構造化出力・リモートMCP・セッション再開(resume)の信頼性が向上しましたMODEL — Claude Fable 5が一般提供。100万トークン文脈・常時アダプティブ思考・128K出力が特徴ですLINEUP — 主力はOpus 4.8・Sonnet 4.6・Haiku 4.5。用途に応じて使い分けられます
記事一覧/API & SDK
API & SDK/2026-06-27上級

Claude API のストリーミングが「エラーも出さずに」止まるとき — 沈黙する停止を検知して途中から続ける運用メモ

Claude API のストリーミングが例外も出さずに無言で止まる「沈黙停止」を、トークン間隔のウォッチドッグで検知し、受信済みテキストを引き継いで途中から再開する実装と、長時間の自動運用で崩れないためのタイムアウト予算の設計をまとめます。

streaming15api38python22production83reliability9sse2

プレミアム記事

ストリーミングのトラブルで厄介なのは、例外が飛んでくれる切断ではなく、何のエラーも出さないまま、ただデルタが届かなくなる停止です。try/except には何もかからない。ログにもスタックトレースは残らない。プロセスは生きていて、ソケットも開いている。それでも本文は途中で終わっている——この「沈黙停止」を、私自身しばらく見逃していました。

個人開発で複数のサイトの記事生成を無人で回していたある時期、生成物のうち数本がいつも末尾の数段落だけ欠けて保存されていました。エラー通知は一度も鳴っていません。原因にたどり着くまで時間がかかったのは、stream.text_stream を最後まで for で回しているのに途中で抜けていた、という挙動が、コードの見た目からはまったく異常に見えなかったからです。本稿は、その沈黙停止を検知して途中から続けるための、実運用で落ち着いた組み立てを残しておくものです。一般的なタイムアウト延長やリトライの話は前提として、その先で必要になった部分に絞ります。

なぜ ReadTimeout は沈黙停止で発火しないのか

多くの解説は「read タイムアウトを延ばせ」で止まります。ところが沈黙停止では、そのタイムアウトがしばしば発火しません。理由は、SDK の read タイムアウトが測っているのがソケットからのバイト到着間隔だからです。

Server-Sent Events の経路には、リバースプロキシやロードバランサーがコメント行(: ping のような無害な行)を一定間隔で流していることがあります。あるいは Anthropic 側の ping イベントが届き続けることもあります。すると、本文のデルタは一つも来ていないのに、ソケットには定期的に何かが届いているという状態が生まれます。socket は「生きている」ので read タイムアウトはリセットされ続け、永遠に発火しません。接続は健康、けれど中身は死んでいる。これが沈黙停止の正体です。

つまり監視すべき粒度が違います。守るべきは「バイトが来ているか」ではなく「意味のあるコンテンツ(text_delta)が進んでいるか」です。ここを測るのが、以下のウォッチドッグです。

トークン間隔ウォッチドッグ

考え方は単純で、最後に text_delta を受け取った時刻を記録し、その間隔がしきい値を超えたら自分から打ち切る、というものです。SDK 任せのソケットタイムアウトとは別の、コンテンツ層の番人を一つ足すイメージです。

import time
import threading
import anthropic
 
class StreamStalled(Exception):
    """意味のあるデルタが一定時間届かなかった(沈黙停止)"""
 
def stream_with_watchdog(
    client: anthropic.Anthropic,
    messages: list,
    model: str = "claude-sonnet-4-6",
    max_tokens: int = 8192,
    stall_seconds: float = 25.0,   # text_delta の途切れ許容時間
):
    """
    text_delta の到着間隔を監視し、stall_seconds を超えたら StreamStalled を投げる。
    受信済みテキストは buf に貯め、停止時でも呼び出し側が引き継げるようにする。
    """
    buf: list[str] = []
    last_delta = {"t": time.monotonic()}
    stop = threading.Event()
 
    def watchdog():
        while not stop.wait(1.0):
            gap = time.monotonic() - last_delta["t"]
            if gap > stall_seconds:
                stop.set()
                # ストリームの基盤接続を閉じて for ループを抜けさせる
                raise_close()
 
    # close 用のフックは stream オブジェクト確定後に差し込む
    closer = {"fn": lambda: None}
    def raise_close():
        closer["fn"]()
 
    wd = threading.Thread(target=watchdog, daemon=True)
 
    with client.messages.stream(
        model=model, max_tokens=max_tokens, messages=messages,
    ) as stream:
        closer["fn"] = stream.close  # ウォッチドッグから接続を閉じられるように
        wd.start()
        try:
            for text in stream.text_stream:
                last_delta["t"] = time.monotonic()
                buf.append(text)
                yield text
        finally:
            stop.set()
 
    received = "".join(buf)
    if last_delta_gap_exceeded(last_delta, stall_seconds) and stream_incomplete(stream):
        raise StreamStalled(received)

実装上のキモは二つあります。一つは、ウォッチドッグから stream.close() を呼べるようにしておくこと。for ループはブロッキングなので、外側から接続を閉じない限り、沈黙したまま待ち続けてしまいます。もう一つは、停止時点までの received を必ず手元に残すことです。これがなければ「途中から続ける」が成立しません。

stall_seconds は経路によって調整します。私の環境では、初トークンまで(思考が長いモデルでは特に)20 秒前後かかることがあるため、初トークン専用にもう少し長い猶予(後述の first_token_seconds)を別に持たせ、いったん本文が流れ始めてからは 25 秒を上限にしています。本文が動き出した後に 25 秒も無言なら、それは経路側の停止とみなして打ち切ったほうが、待ち続けるより回復が速いという判断です。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
ReadTimeout が発火しない『沈黙停止』を、ソケット読み取りではなくトークン間隔で測るウォッチドッグの実装
受信済みテキストを assistant プレフィルとして引き継ぎ、最初から再生成せずに途中から続ける再開ロジックと、重複の刈り取り方
長時間バッチで崩れないための『接続・初トークン・トークン間・全体』4層のタイムアウト予算と、その実測の取り方
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

API & SDK2026-03-31
Claude API ストリーミング × リアルタイムチャットUI 本番実装ガイド
Claude APIのストリーミングレスポンスとServer-Sent Eventsを活用し、Next.js App RouterでリアルタイムチャットUIを本番品質で組み上げる手順を、SSEプロトコル・Tool Use統合・接続復旧・コスト最適化まで通しで整理します
API & SDK2026-05-06
Claude API × Python 実践:ツール呼び出しとストリーミングを組み合わせてAIアシスタントを作る
Claude API の Tool Use とStreamingを同時に使うPython実装を解説。ツールを定義してリアルタイム応答するAIアシスタントの完成コードと、組み合わせ時に詰まりやすいポイントを丁寧に解説します。
API & SDK2026-04-26
Claude API × WebSocket × Redis Pub/Sub でスケーラブルなリアルタイム AI チャットサーバーを構築する — Node.js 本番設計・マルチユーザー対応・コスト制御の実装
Claude API × WebSocket × Redis Pub/Sub を使ったリアルタイム AI チャットサーバーの本番実装。SSE との使い分け、Node.js でのスケーラブルな接続管理、ユーザーごとのコスト制御まで完全解説。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →