Claude Media
大きいメッセージでJSONDecodeErrorが発生し停止する原因と対処

大きいメッセージでJSONDecodeErrorが発生し停止する原因と対処

Python版Claude Agent SDKで大きな応答時にCLIJSONDecodeErrorが起きる原因と、現行SDKでのmax_buffer_size対処をまとめます。

大きいメッセージでJSONDecodeErrorが起きるとどうなるか

Python版のClaude Agent SDK(claude-agent-sdk)には、Claudeが長い応答を返した瞬間にCLIJSONDecodeError(内部的にはjson.JSONDecodeErrorをラップした例外)が発生し、メッセージの受信そのものが止まってしまう不具合が過去に存在しました。エラーはjson.decoder.JSONDecodeError: Unterminated string starting at: ...という形で現れ、CLIが送ってきたはずのJSON文字列が途中で切れていることを示します。原因はサブプロセスの標準出力を、1行分のJSONとしてではなく任意の長さの「チャンク」単位で読んでいたことにありました。

このバグはclaude-agent-sdk-python issue #32として2025年6月21日に報告され、現在はクローズ済みです。ただし報告時点のパッケージ名は今のclaude-agent-sdkではなくclaude-code-sdkで、再現コードもclaude_code_sdkをimportしています。SDKは2025年9月のリネームでパッケージ名とクラス名(ClaudeCodeOptionsClaudeAgentOptions)が変わっており、issue本文のコードをそのまま最新環境で動かそうとすると別の理由で失敗します。この記事では当時何が起きていたかと、現行のSDKでこの種のエラーに遭遇したときの切り分け方をまとめます。

原因 — サブプロセスの出力はJSON行単位で届かない

Claude Agent SDKは内部でClaude Code CLIをサブプロセスとして起動し、標準出力からNDJSON(1行1メッセージのJSON)を読み取ります。issue報告当時のコードは、この標準出力をanyio.open_process()経由で読んでいましたが、明示的なバッファサイズを指定していませんでした。OSのデフォルトのパイプバッファは8KB〜64KB程度で、Claudeが長いテキストを1つのJSONメッセージとして返すと、この上限を超えた部分が別のチャンクとして届きます。

問題は、チャンクの区切りがJSON文字列の途中に落ちることがある点です。1つのメッセージが複数回の読み取りにまたがると、最初のチャンクだけをjson.loads()に渡した時点で「閉じられていない文字列」としてパースが失敗します。issueの報告者はこれをsubprocess_cli.pybufsize未指定が原因と特定し、バッファを10MBに増やす修正案を提示していました。issue本文は関連issueとして#31・#15・#6を挙げており、同種のJSON解析失敗が単発ではなく繰り返し報告されていたことがうかがえます。

再現条件とissue報告当時のエラー

issueの再現コードは、当時のclaude_code_sdkパッケージで長い応答を要求するプロンプトを送るというものでした。

import asyncio
from claude_code_sdk import query, ClaudeCodeOptions
 
async def test_long_response():
    options = ClaudeCodeOptions(
        allowed_tools=["*"],
        permission_mode="bypassPermissions",
    )
    async for message in query(
        prompt="長い応答を返すプロンプト",
        options=options,
    ):
        print(f"Message type: {type(message).__name__}")

報告時の環境はclaude-code-sdk 0.0.11・Python 3.12・Linux(WSL2)でした。エラーはCLIJSONDecodeErrorとして送出され、query()が返す非同期ジェネレータの内部で起きるため、呼び出し側でtry/exceptを書いていても以降のメッセージは受け取れずにループそのものが終わります。

バッファサイズを増やすだけの回避策が効かなかった理由

issueのコメント欄では、bufsizeを増やす修正やフォーク版を試した複数の利用者が、それでも症状が再発したと報告しています。2025年6月30日付のコメントでは、提案された修正やフォークを適用した後もほぼ同じCLIJSONDecodeErrorが出ていると記録されています。バッファを大きくするだけでは根本的な解決にならなかったことになります。

理由は単純です。バッファサイズを増やしても、読み取り自体が「チャンク単位」であることは変わりません。1回のreceive()が返す範囲がどこで切れるかはストリーム側の都合で決まり、送信側のバッファがどれだけ大きくても、受信側がチャンクを正しく連結してからパースする実装になっていなければ、境界がJSON文字列の途中に落ちる可能性は残ります。必要だったのは容量を増やすことではなく、改行文字を区切りとして完全な1行を組み立ててからパースする「行の再構成」でした。

issueのコメントには、anyio.open_process()の代わりにanyio.run_process()を使ったら解決したという報告もありました。ただしrun_process()は出力全体を読み終えてからまとめて返す高レベルAPIで、メッセージが届くたびに逐次処理するストリーミング用途には向きません。SDKが採用したのは低レベルのopen_process()を使い続けたまま、アプリケーション層で行の再構成を行う方式です。

現在のSDKは行を正しく組み立て直している

最新のSDKは_LineFramerという内部クラスでこの問題に対処しています。ソースコードのコメントには、anyioのTextReceiveStreamが返すのは行ではなくチャンク(asyncioバックエンドでは最大64KiB)であり、チャンクの境界はJSON文字列の値の内側にも落ちうる、と明記されています。

_LineFramerは受け取ったチャンクをリストに蓄積し、改行文字が現れた時点でそれまでの断片を結合して完全な行を切り出します。改行がまだ来ていない断片は次のチャンクに持ち越されるため、1つのJSONメッセージがどれだけ多くのチャンクに分割されて届いても、パースに渡されるのは常に完全な1行です。標準出力側だけでなく、stderrコールバックに渡すログ行も同じ_LineFramerで組み立てられています。

行が完全に組み立てられた状態でパースするため、json.JSONDecodeErrorが発生した場合はそれ以上データを待っても直らない本物の破損データだとみなし、_parse_stdout_line()はその場でCLIJSONDecodeErrorを送出します。切り詰めによる見かけ上のエラーと、本当に壊れたJSONを取り違えない設計です。

バージョン別の修正状況

issueの報告から現在までの主な変更は次のとおりです。パッケージ名は2025年9月のリネームより前がclaude-code-sdk、以降がclaude-agent-sdkです。

日付バージョン変更
2025-06-18バージョンclaude-code-sdk 0.0.11変更issue報告時の環境(未修正)
2025-06-21バージョン変更issue #32報告
2025-06-28バージョンclaude-code-sdk 0.0.13変更「複数行にまたがるバッファリングの不具合を修正」(CHANGELOG)
2025-07-01バージョンclaude-code-sdk 0.0.14変更「複数回のストリーム読み取りにまたがって分割されたJSON出力の処理を改善」
2025-09-28バージョンclaude-agent-sdk 0.1.0変更パッケージ名をclaude-agent-sdkにリネーム。ClaudeCodeOptionsClaudeAgentOptions
2026-03-27バージョンclaude-agent-sdk 0.1.51変更「CLI標準出力の非JSON行をスキップしバッファ破損を防止」(#723)
2026-07-06バージョンclaude-agent-sdk 0.2.111変更「64KiBのストリームバッファ境界をまたぐ大きなNDJSON行で発生していた空白文字の消失を修正」(#1083)
2026-09-18バージョンclaude-agent-sdk 0.2.157変更最新版

issue本文が示した「bufsizeを増やすだけの対処」は初期の0.0.13前後で試みられましたが、コメント欄の報告どおり完全な解決には至らず、0.0.14以降で行の組み立て自体を改善する方向に切り替わっています。その後も#723#1083のように、チャンク境界をまたぐケースの細部が段階的に修正されてきました。issue自体が2025年6月という1年以上前の報告である点は、検索でこのissueに直接たどり着いた場合に見落としやすいところです。

max_buffer_sizeで上限を制御する

行の組み立て自体は自動で行われますが、1つのメッセージが際限なく大きくなることを防ぐため、ClaudeAgentOptionsにはmax_buffer_sizeという上限値があります。公式ドキュメントでは既定値Noneと記載されていますが、NoneのときはSDK内部の_DEFAULT_MAX_BUFFER_SIZE(1MB)にフォールバックします。累積中の行、または確定した1行がこの上限を超えると、切り詰めではなく明示的なCLIJSONDecodeErrorが発生します。

JSON message exceeded maximum buffer size of 1048576 bytes

このメッセージが出た場合は、破損したデータではなく1メッセージが1MBという既定の上限を超えたことを示しています。巨大なツール出力やファイル内容をそのままメッセージに含める使い方をしている場合、max_buffer_sizeを明示的に引き上げると解消します。

from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
 
options = ClaudeAgentOptions(
    cwd=".",
    max_buffer_size=10 * 1024 * 1024,  # 10MB
)
client = ClaudeSDKClient(options)

上限を大きくするほどメモリ使用量も増えるため、実際に扱う最大メッセージサイズに合わせて設定するのが妥当です。

それでもJSONDecodeErrorが出た場合の切り分け

  1. エラーメッセージの文言を見るexceeded maximum buffer sizeという文言があれば、切り詰めではなくmax_buffer_sizeの上限を実際に超えています。前節の対処が該当します
  2. .line.original_error属性を読むCLIJSONDecodeError(from claude_agent_sdk import CLIJSONDecodeErrorでimport可能)は問題の行の先頭部分と、元のjson.JSONDecodeErrorを保持しています。ログに残しておくと再現条件を絞り込みやすくなります
  3. インストール済みバージョンを確認する0.2.111より前のバージョンでは、大きな行で空白文字が失われる別の不具合(#1083)が残っている可能性があります

query()ClaudeSDKClient.receive_messages()はどちらも同じSubprocessCLITransportを経由するため、切り分けの手順はどちらのAPIを使っていても共通です。CLIの起動失敗やプロセス終了系の別のエラーは原因の系統そのものが異なります。

状況別の対処早見表

状況対処
新規にSDKを導入する対処最新版(0.2.157以降)を使う。このバグには最初から触れない
既存プロジェクトでバージョンを固定していない対処pip install --upgrade claude-agent-sdkで更新するだけで行の組み立て不備は解消する
claude-agent-sdk 0.2.111より前を使い続けている対処0.2.111以降へ更新する。64KiB境界をまたぐ大きな行での空白消失(#1083)が残っている可能性がある
ツール出力やファイル内容を丸ごとメッセージに含めている対処ClaudeAgentOptions(max_buffer_size=...)で上限を明示的に引き上げる
すぐに更新できない事情がある対処エラーメッセージの文言(exceeded maximum buffer sizeか否か)で原因を切り分けてから対処を決める
pip install --upgrade claude-agent-sdk
python -c "from claude_agent_sdk import __version__; print(__version__)"

まとめ

Python版Claude Agent SDKのCLIJSONDecodeErrorは、2025年6月に報告された古いissueでは「サブプロセスのパイプバッファが小さく、大きなJSONメッセージが途中で切れる」という不具合でした。単純にバッファサイズを増やす対処は根本解決にならず、SDKはその後_LineFramerによってチャンクを改行単位で完全な1行に組み立て直してからパースする設計に変わっています。現行のSDKでこの例外を見たら、まずメッセージの文言を確認してください。exceeded maximum buffer sizeとあれば、それは切り詰めではなくmax_buffer_size(既定1MB)という明示的な上限を超えたサインで、対処はオプションの引き上げです。それ以外のCLIJSONDecodeErrorは、0.2.111より前のバージョンを使っていないか確認するのが最初の一手になります。

大きな応答を安定して受け取れても、CLI自体の起動に失敗する・プロセスが途中で終了するといった別系統のエラーは原因が異なります。Agent SDKのエラー集で例外の型別に整理しているので、あわせて確認してください。rate_limit_event受信時にMessageParseErrorで止まる別の既知バグはrate_limit_eventでMessageParseErrorが発生し停止する原因と対処にまとめています。ClaudeAgentOptionsの他のフィールドでよく踏むつまずきはClaudeCodeOptionsのcwdが反映されない理由と回避策、SDKを初めて使う場合はClaude Agent SDK入門を先に読むと全体像がつかみやすくなります。

この記事を共有:XはてブLinkedIn