大きいメッセージでJSONDecodeErrorが発生し停止する原因と対処
Python版Claude Agent SDKで大きな応答時にCLIJSONDecodeErrorが起きる原因と、現行SDKでのmax_buffer_size対処をまとめます。
大きいメッセージでJSONDecodeErrorが起きるとどうなるか
Python版のClaude Agent SDK(claude-agent-sdk)には、Claudeが長い応答を返した瞬間にCLIJSONDecodeError(内部的にはjson.JSONDecodeErrorをラップした例外)が出て、メッセージの受信が止まる不具合が過去にありました。エラーはUnterminated string starting at: ...という形で現れ、CLIが送ったはずのJSON文字列が途中で切れていることを示します。原因は、サブプロセスの標準出力を「1行1メッセージ」ではなく、任意の位置で切れるチャンク単位のまま扱っていたことでした。
この不具合はclaude-agent-sdk-python issue #32として2025年6月21日に報告され、6月28日にクローズされています。ただし報告時のパッケージ名は今のclaude-agent-sdkではなくclaude-code-sdk(0.0.11)で、再現コードもclaude_code_sdkをimportしています。2025年9月のリネームでパッケージ名とClaudeCodeOptions→ClaudeAgentOptionsが変わったため、issueのコードを今の環境にそのまま貼っても別の理由で動きません。
以降は、当時の原因、現行SDKの読み取りの仕組み、最近のSDKで実際に出る別系統のCLIJSONDecodeErrorの順に説明します。
原因 — サブプロセスの出力は行単位で届かない
Claude Agent SDKは内部でClaude Code CLIをサブプロセスとして起動し、標準出力からNDJSON(1行1メッセージのJSON)を読みます。issue本文は、当時のsubprocess_cli.pyがanyio.open_process()を呼ぶ際にバッファサイズを指定していなかったことを原因として挙げ、既定のパイプバッファ(8KB〜64KB程度)より長いJSONが途中で切れると書いています。提案された修正はbufsize=10 * 1024 * 1024の指定でした。
現行ソースのコメントは、問題の実体をもう少し正確に説明しています。anyioのTextReceiveStreamが返すのは行ではなくチャンクで、asyncioバックエンドでは1回のreceive()が最大64KiBです。大きな1行は複数のチャンクにまたがり、境目はJSON文字列の内側にも落ちます。最初のチャンクだけをjson.loads()に渡せば、「閉じられていない文字列」として失敗します。
issue上の経緯 — バッファを増やしても直らなかった
issueのコメント欄では、対処がすぐには収束しなかったことが読み取れます。6月25日には、open_processをrun_processに置き換えたら動いたという報告がありました。6月26日にはバッファサイズが原因だとして、修正を入れたフォークが紹介されています。
6月28日に「最新リリースで直った」というコメントが付き、issueはクローズされました。ところが6月29日には、最新リリースでも再発するという報告がありました。6月30日にはPR #34の修正やフォークを入れても同じCLIJSONDecodeErrorが出るという報告が続きました。バッファを大きくしても、読み取りがチャンク単位であることは変わらないためです。受信側が改行で1行を組み立ててから渡す実装でなければ、境目がJSON文字列の途中に落ちる可能性は残ります。
run_process()は出力全体を読み終えてから返す高レベルAPIで、メッセージごとに逐次処理するストリーミング用途には合いません。SDKはopen_process()を使い続け、読み取った側で行を組み立てる方式に進みました。
現行SDKは行を組み立ててからパースする
最新のSDKでは、_LineFramerという内部クラスが行の組み立てを担当します。受け取ったチャンクをリストに溜め、改行を含むチャンクが来た時点で結合して、完成した行だけを返します。標準出力だけでなく、stderrコールバックに渡すログ行も同じ_LineFramerで組み立てられます。
組み立てた行は_parse_stdout_line()でパースされます。ここでjson.JSONDecodeErrorが出れば、待っても直らない破損データとみなしてCLIJSONDecodeErrorをその場で送出します。{で始まらない行(CLIが標準出力に混ぜるデバッグ出力など)は、エラーにせず読み飛ばします。
仮想環境で動きを確かめる
claude-agent-sdk 0.2.163を一時的な仮想環境に入れ、約20万文字の1行を64KiBごとの4チャンクに切って_LineFramerに渡しました。_LineFramerは内部クラスなので、公開APIではなく挙動の確認用です。
import json
from claude_agent_sdk import CLIJSONDecodeError
from claude_agent_sdk._internal.transport.subprocess_cli import (
_LineFramer, _parse_stdout_line)
text = "x " * 100000
msg = json.dumps({"type": "assistant", "text": text}) + "\n"
chunks = [msg[i:i + 65536] for i in range(0, len(msg), 65536)]
print("message chars:", len(msg), "chunks:", len(chunks))
try:
json.loads(chunks[0])
except json.JSONDecodeError as e:
print("first chunk only ->", type(e).__name__ + ":", e)
framer = _LineFramer()
for i, c in enumerate(chunks):
out = framer.push(c)
print("chunk", i, "-> lines:", len(out), "pending_len:", framer.pending_len)出力は次のとおりです。最初のチャンクだけをパースすると、issueと同じUnterminated stringで失敗します。
message chars: 200034 chunks: 4
first chunk only -> JSONDecodeError: Unterminated string starting at: line 1 column 31 (char 30)
chunk 0 -> lines: 0 pending_len: 65536
chunk 1 -> lines: 0 pending_len: 131072
chunk 2 -> lines: 0 pending_len: 196608
chunk 3 -> lines: 1 pending_len: 03つ目のチャンクまでは何も返さず、改行を含む4つ目で完全な1行が出ます。その行を_parse_stdout_line()に渡すと元のテキストが復元できました。同じ関数に[SandboxDebug] helloを渡すとNoneが返り(読み飛ばし)、途中で切れたJSONを渡すとCLIJSONDecodeErrorが出て、.lineに問題の行、.original_errorにJSONDecodeErrorが入っていました。
近年のバージョンで直った関連バグ
issue #32の後も、チャンク境界まわりの修正が続いています。時系列は次のとおりです。
issue報告から現在までの主な修正
- 2025-06-18claude-code-sdk 0.0.11
issue報告時の環境です。
- 2025-06-28claude-code-sdk 0.0.13
CHANGELOGに「複数行にまたがるバッファリングの不具合を修正」とあります。issue #32はこの日にクローズされました。
- 2025-07-01claude-code-sdk 0.0.14
「複数回のストリーム読み取りにまたがって分割されたJSON出力の処理を改善」とあります。
- 2025-09-28claude-agent-sdk 0.1.0
パッケージ名を
claude-agent-sdkにリネームしました。 - 2026-03-27claude-agent-sdk 0.1.51
CLI標準出力の非JSON行を読み飛ばし、バッファの破損を防ぐ修正です(#723)。
- 2026-07-06claude-agent-sdk 0.2.111
1行が64KiBのストリームバッファを超えたとき、パーサーが空白文字を黙って落とす問題を修正しました(#1083)。ツール出力やアシスタントの文面が壊れる可能性があった不具合です。
0.2.111の修正は、メッセージが大きい環境にとっていちばん関係が深いものです。この不具合は例外にならず、空白だけが欠けた本文が返ります。CLIJSONDecodeErrorが出ないぶん、気づきにくい症状です。
max_buffer_sizeで1メッセージの上限を決める
行の組み立ては自動ですが、1メッセージが際限なく大きくなるのを防ぐため、ClaudeAgentOptionsにはmax_buffer_sizeがあります。公式ドキュメントの既定値はNoneで、NoneのときはSDK内部の_DEFAULT_MAX_BUFFER_SIZE(1024 × 1024、つまり1MB)が使われます。組み立て中の行が上限を超えた時点、または完成した1行が上限を超えたときに、次のCLIJSONDecodeErrorが出ます。
JSON message exceeded maximum buffer size of 1048576 bytesこの文言が出たなら、データの破損ではなく、1メッセージが上限を超えたという意味です。巨大なツール出力やファイル内容がそのまま1メッセージに入る使い方では、max_buffer_sizeを引き上げると収まります。
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
options = ClaudeAgentOptions(
max_buffer_size=10 * 1024 * 1024, # 10MB
)
client = ClaudeSDKClient(options)ソース上は、上限の比較にlen(line)、つまり文字列の長さを使っています。エラー文言はbytesと書きますが、日本語など1文字が複数バイトになる本文では、バイト数が上限を超えていても通ることがあります。上限は、扱う最大メッセージの文字数を目安に決めます。大きくするほど、1行を溜めるぶんのメモリも増えます。
それでもCLIJSONDecodeErrorが出たとき
原因の切り分け
- 1
エラー文言を読む
exceeded maximum buffer sizeとあれば、切り詰めではなくmax_buffer_sizeの上限超えです。このとき.lineには元の行ではなく文言が入ります。前節のとおり上限を引き上げます。 - 2
`.line`と`.original_error`を残す
CLIJSONDecodeError(from claude_agent_sdk import CLIJSONDecodeError)は、問題の行と元の例外(通常はJSONDecodeError、上限超過のときはValueError)を属性に持ちます。ログに出しておくと、どの行のどの位置で壊れたかを後から追えます。 - 3
インストール済みバージョンを見る
0.2.111より前なら、64KiBを超える行で空白が落ちる別の不具合(#1083)の影響を受けている可能性があります。更新して再現するかを確かめます。
エラーにならずに欠ける場合もあります。標準出力がCLIの強制終了などで途中で終わると、改行のない末尾の断片は、CLIJSONDecodeErrorにならず、デバッグログだけを残して捨てられます。メッセージが途中で止まったように見えるのに例外が出ないときは、CLIプロセスの終了コードやstderrを先に見る価値があります。
状況別の対処
| 状況 | 対処 |
|---|---|
| 新規にSDKを導入する | 対処現行のclaude-agent-sdkを使えば、行の組み立ては最初から入っています |
| バージョンを固定していない既存プロジェクト | 対処pip install --upgrade claude-agent-sdkで更新します |
| ツール出力やファイル内容を丸ごとメッセージに含める | 対処max_buffer_sizeを引き上げます |
pip install --upgrade claude-agent-sdk
python -c "from claude_agent_sdk import __version__; print(__version__)"まとめ
2025年のissue #32のようにバッファサイズだけを変える対処は、今のSDKでは必要ありません。今出るCLIJSONDecodeErrorは、max_buffer_sizeの上限超過か、破損したデータのどちらかです。
大きな応答を受け取れても、CLIの起動失敗やプロセスの途中終了は別系統のエラーです。例外の型別の整理はAgent SDKのエラー集にあります。rate_limit_eventでMessageParseErrorが出て止まる別の既知バグはrate_limit_eventでMessageParseErrorが発生し停止する原因と対処に、ClaudeAgentOptionsの他のフィールドでよく踏むつまずきはClaudeCodeOptionsのcwdが反映されない理由と回避策にまとめています。SDKを初めて使う場合はClaude Agent SDK入門が全体像をつかむ近道です。