Stream Closed Before Completed
4つの原因と、ホップごとの切り分け法
ストリーミング応答が途中で切れ、エラー文言は "stream closed before completed"——一般的な原因は4つあり、まったく異なる問題であるため、ホップごとに切り分けないと正しい原因にたどり着けない。
4つの重要事実
4つの一般的な原因
上流のレート制限フレーム、タイムアウトの不整合、クライアントの切断、書き換え層による終結イベントの消失——現象は似ているが根本原因はまったく異なる。
切り分け法の核心
クライアントからモデルサービスまでは通常複数ホップを経由する(クライアント→ゲートウェイ→ロードバランサー→上流)。どのホップで切断が起きたかはホップごとに確認するしかない。
終結イベントが届いたかどうか
多くのストリーミングプロトコルは終結イベント([DONE] など)で正常終了を示す。途中でこれが見えないまま切れた場合、正常完了ではなく途中で打ち切られたことを意味する。
最も見落とされがちな原因
クライアント、ゲートウェイ、ロードバランサーはそれぞれ独自のタイムアウトを設定していることが多い。いずれか1つのホップのタイムアウトがモデルの実際の生成時間より短ければ、そのホップで強制的にストリームが切断される。
4つの原因はそれぞれ何か
1つ目、上流のレート制限フレーム:モデルサービスが生成中にレート制限にかかり、生成を早期終了させる中断信号を挿入する。クライアント側から見ると、ストリームが終わる前に切れたように見える。2つ目、タイムアウトの不整合:リクエストはクライアント、プロキシ、ゲートウェイ、ロードバランサーなど複数ホップを経由する。いずれか1つのホップに設定されたタイムアウトがモデルの実際の生成時間より短ければ、モデル側は正常に生成を続けていても、そのホップが接続を強制的に切断する。3つ目、クライアントの能動的な切断(client gone):ユーザーが途中でページを閉じた、リクエストをキャンセルした、あるいはクライアント側のコンテナ/プロセスが回収された場合で、この種の中断は通常サーバー側ログに「peer closed connection」のような記録を残し、上の2つとは逆方向の現象だ。4つ目、書き換え層による終結イベントの消失:一部のリバースプロキシ、CDN、書き換えミドルウェアはストリーミング応答本体を再処理する。実装に不具合があると、実際のコンテンツは正しく転送しつつ、「生成が正常に完了した」ことを示す終結イベント(SSE の [DONE] など)だけを取りこぼすことがある。その結果クライアントは接続が異常切断されたと判断するが、実際にはモデル側は正常に生成を完了している。
この切り分け法を覚えておく価値
4つの原因のうち、最初の2つ(レート制限フレーム、タイムアウトの不整合)は「自分たちのネットワークが不安定だ」と誤診されやすい。3つ目(クライアントの能動的切断)はサーバー側から「モデルが不安定だ」と誤って責任転嫁されがちだ。4つ目(書き換え層による終結イベントの消失)は最も見つけにくい——クライアントが受け取った内容自体は完全に見えるため、象徴的な終了信号が1つ欠けているだけなのに、体系的な問題ではなく「たまたまのバグ」として片付けられやすいからだ。
タイムライン
ストリーミング応答(SSE など)とその終結イベントの仕組みは長く存在する標準設計であり、4つの中断原因もストリーミングプロトコルの普及とともにずっと存在している。
エージェント化されたワークフローにより長時間のストリーミング生成が増え、経路上のどこかのホップのタイムアウト設定が不適切な場合により表面化しやすくなっている。
最も見つけにくい「書き換え層による終結イベントの消失」は、リバースプロキシ/CDN ミドルウェアの複雑化に伴い、発生頻度が上がっている。
確認済み vs よくある誤解
確認済み
「stream closed before completed」というメッセージは公開されている開発者の議論に一貫して現れる。4つの原因(レート制限フレーム、タイムアウトの不整合、クライアントの能動的切断、書き換え層による事象消失)はそれぞれ独立して裏付けがあり、別々に切り分ける必要がある。
よくある誤解
ストリーム中断に遭遇すると、多くの人はまず「モデルが不安定」「ベンダー側のサービスに問題がある」と考える。しかし実際には、タイムアウトの不整合と書き換え層のバグ——どちらもクライアント/ミドルウェア側の設定や実装の問題——がかなりの割合を占めている。
4つの原因をどう見分けるか
サーバー側による能動的な切断(レート制限フレーム / タイムアウト)
サーバーログには、この生成がレート制限にかかったか、いずれかのホップのタイムアウトで中断がトリガーされたことが記録される。特徴として、同じリクエストでも時間帯を変えたり並行数を下げたりすると正常に完了することが多い。
クライアント側の問題(能動的切断 / 書き換え層による事象消失)
サーバーログでは生成自体は正常に完了しており、問題はクライアントが早すぎるタイミングで切断した、または途中の書き換え層が終結イベントを転送しなかったことにある。この場合はクライアント/プロキシの設定を調べるべきで、モデルサービス側の問題ではない。
ホップごとの切り分け法
第1歩、サーバーログを見る:今回の生成が最後まで完了したか、レート制限にかかったか。サーバー記録が正常完了を示していれば、問題はクライアントとサーバーの間のどこかのホップにある可能性が高い。第2歩、ホップごとにタイムアウト設定を確認する:クライアントのタイムアウト、CDN/ゲートウェイのタイムアウト、ロードバランサーのタイムアウト——どれがモデルの実際の生成時間より短いかを特定する。第3歩、書き換え層による事象消失を疑う場合は、「サーバーが送信した生の流」と「クライアントが受け取った流」を直接比較し、終結イベントが途中で失われていないか確認する。第4歩、クライアントが意図的に切断した場合(ユーザーが手動でキャンセルしたなど)、それ自体は障害ではない。アプリケーションロジック側でそのキャンセルケースを正しく処理すればよく、システム障害として調査すべきではない。
QCode 上での対処法
QCode のエッジノードはストリーミング応答のタイムアウトを整合させており、経路上のどこかのホップのタイムアウトの不整合で長時間の生成が誤って切断されるケースを減らしている。自前のプロキシ層や書き換えミドルウェアに同様の問題がある場合、QCode が提供するエンドポイントに切り替えて一時的に切り分けることもできる。
よくある質問
ストリーム中断が自分側の問題かどうか、どう判断しますか?
まず(取得できるなら)サーバーログを見る:生成が正常に完了していると記録されていれば、問題はクライアントとサーバーの間のどこかのホップ(タイムアウト設定、書き換え層など)にある可能性が高く、自分側で調査・修正できる範囲だ。
なぜ同じリクエストでも正常なときと中断するときがあるのですか?
最も一般的な原因はタイムアウトの不整合だ。モデルの生成時間そのものにばらつきがあり、たまたま経路上で最も短いタイムアウト閾値を超えたときに切断され、生成が短時間で終わったリクエストは正常に完了する。
「クライアントの能動的切断」は障害に含まれますか?
厳密には含まれない——正常なユーザーの行動(リクエストのキャンセル、ページを閉じる)や、クライアント環境の変化(プロセスの回収)によるものであり、システム障害として調査するのではなく、アプリケーションロジック側で正しく識別・処理すべきものだ。
書き換え層による終結イベントの消失は、どう調べるのが一番早いですか?
パケットキャプチャやログを追加し、サーバーが実際にストリーミングした内容とクライアントが最終的に受け取った内容を直接比較する。サーバーが終結イベントを送信していてクライアントが受け取っていなければ、問題は途中の書き換え/転送層にある。
このエラーに遭遇したら、まず何をすべきですか?
まずサーバーログにレート制限やエラーの記録がないか確認する。なければ、クライアント、ゲートウェイ、ロードバランサーそれぞれのタイムアウト設定を照合する。この2つのステップで4つの原因のうち3つを除外できる。
この問題は 429/529 と同じですか?
違う。429/529 はリクエストが生成を始める前に拒否される問題であり、stream closed before completed は生成がすでに始まりストリームが確立された後、完了前に中断される問題だ。発生する段階がまったく異なる。
情報源
4つの原因は、公開されている開発者の議論、SSE などストリーミングプロトコルの標準的な挙動、およびホップごとのタイムアウト切り分けという一般的な運用手法をもとに整理した。本ページは特定のベンダーの非公開実装について断定するものではない。2026-08-27 時点で整理。
関連記事
Context Length Exceeded 完全トラブルシューティングガイド
同じく誤診されやすいもう1つのエラー。見分け方を対照できる。
API Key 利用状況の確認完全ガイド
自分の呼び出し履歴と異常を体系的に確認する方法。
Claude 529 vs 429 vs Weekly Limit
混同しやすいもう1組のエラーの見分け方。
本ページはベンダー横断の一般的な技術解説であり、特定のベンダーの非公開実装について断定するものではありません。実際の挙動は使用するモデル、クライアント、プロキシ経路の設定に依存します。