【Claude Code 連載 第97回】Agent SDK のトラブルシューティング(agent-sdk/troubleshooting)

スポンサーリンク
【Claude Code 連載 第97回】Agent SDK のトラブルシューティング(agent-sdk/troubleshooting) 用語解説
【Claude Code 連載 第97回】Agent SDK のトラブルシューティング(agent-sdk/troubleshooting)
この記事は約20分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

スポンサーリンク

背景

この連載では、Claude Codeの公式ドキュメントを1ページずつ読み解いていきます。公式ドキュメントは情報が網羅されている分、「結局どの機能を、どんな場面で使えばいいのか」は自分で考える必要があり、読むのに意外と時間がかかります。そこで、私が実務で使うために読み込んだ内容を「使う場面→実例コード」の順に整理して残していくことにしました。専門家の解説というより、一次情報を読んだ記録です。推測や動作を確認していない部分には、その都度そう書きます。

公式ドキュメント:https://code.claude.com/docs/ja/agent-sdk/troubleshooting

1. 一言でいうと

Agent SDK を使ったアプリケーションで出るエラーのうち、「Claude Code CLI が起動しない」「CLI のプロセスが途中で終わった」「成功したのに構造化出力が空」の3種類について、表示されるエラーメッセージごとに原因と直し方をまとめたページです。それ以外の症状(フックが発火しない、スキルが見つからない、MCP サーバーが failed になる など)は、各機能のページのトラブルシューティングに振り分ける「案内板」の役目も持っています。

第94回で、手元では動くのにデプロイ先で失敗する3つの型(CLI が見つからない/起動しない/途中で終わる)を見たとき、詳しい対処はこのページに回されていました。第96回で見た「テレメトリの送信失敗は黙って捨てられ、stderr で診断を読む」という話とも、stderr コールバックという同じ道具でつながります。


2. どういう場面で役立つか

シーン1:開発機では動くのに、サーバーやコンテナで起動しない

ターミナルからは動くのに、サービスとして起動すると CLINotFoundError が出る、イメージをビルドし直したら起動しなくなった、という場面です。このページは、メッセージの文面からどの原因かを切り分ける表を持っています。

シーン2:例外は出たが、何が起きたのか分からない

Python で ProcessError が出たが、Error output の行には「stderr を確認してください」としか書いていない、という場面です。原文は、この行が固定の文字列で、実際のエラー出力ではないことを明言しています。本当の原因をどこで読むかが分かります。

シーン3:構造化出力が成功なのに空

結果は success なのに、受け取るはずの structured_output が None(TypeScript では undefined)で、後続の処理が落ちる、という場面です。

不要・向かないケース

  • 機能ごとの症状:フック、スキル、MCP、プラグイン、サブエージェント、ファイルチェックポイントの不具合は、それぞれのページに対処が書かれています(3-1 の案内表)。このページを探しても答えはありません。
  • 認証や API のエラー:Not logged in、Invalid API key、API Error、429、There's an issue with the selected model は、本体側のエラーリファレンスの担当です。
  • ここに載っていないエラー:原文の指示は、SDK のリポジトリ(TypeScript/Python)の issue を確認し、なければ新しく報告することです。その際、エラーの全文と SDK のバージョンを含めます。

3. コードと仕組みの解説

原文の fenced コードブロックは6本で、すべてエラーメッセージの実例です(実行するコードはありません)。どれも別のエラーなので全数を引用します。原文から機械的に抜き出したもので、変えていません。メッセージ中の <detail> や <tail of the CLI's stderr> のような山かっこは、実際には具体的な内容が入る場所を示す原文の表記です。

3-0. バージョン要件

変化バージョン
Windows で .bat/.cmd のバッチスクリプトを CLI として起動するのを拒否するようになった(それより前は cmd.exe 経由でそのまま起動していた)claude-agent-sdk(Python)0.2.124 以降
CLI がエラー結果を報告して終了したとき、単純な Exception ではなく ResultError を送出するようになったclaude-agent-sdk(Python)0.2.140 以降

どちらも Python SDK の変化です。2行目は例外の捕まえ方に直接効くので、古い SDK から上げるときは except 節を見直す必要があります(筆者の指摘)。

3-1. どのページを見ればよいか(案内表)

原文の冒頭の表を、症状と行き先に分けて示します。

症状行き先
スキルが見つからない、使われない、Invalid skill nameスキルのページ(第90回)
MCP サーバーが failed、ツールが呼ばれない、SDK MCP サーバーにツールが無い、接続タイムアウト、ツール出力が上限トークン数を超えるMCP のページ(第84回)
プラグインが読み込まれない、プラグインのスキルが出ないプラグインのページ(未執筆)
Claude がサブエージェントに委任しない、ファイルで定義したエージェントが読み込まれないサブエージェントのページ(第89回)
チェックポイントのオプションが認識されない、UUID の無いユーザーメッセージ、No file checkpoint found、File rewinding is not enabled、ProcessTransport is not ready for writingファイルチェックポイントのページ(第88回)
フックが発火しない、マッチャーが期待どおり絞らない、フックのタイムアウト、ツールが予期せずブロックされる、変更した入力が反映されない、Python でセッションのフックが使えない、サブエージェントの権限確認が増える、サブエージェントとのフックの再帰ループ、systemMessage が出力に出ないフックのページの「一般的な問題を修正」(第82回)
手元では動くエージェントが、デプロイしたサービスやコンテナで失敗するホスティングのページの「デプロイメント失敗のトラブルシューティング」(第94回)
Not logged in、Invalid API key、API Error、429、There's an issue with the selected model本体側のエラーリファレンス(未執筆)
CLINotFoundError、CLIConnectionError、ProcessError、Claude Code process exited with code N、Claude Code returned an error result、structured_output が Noneこのページ(3-2〜3-4)

「行き先」の列の回数は筆者が連載の対応回を足したものです。原文がこれだけ細かく振り分けているのは、SDK のエラーの多くが「SDK の不具合」ではなく「各機能の設定の問題」だから、と読めます(筆者の解釈)。以下、このページが自分で受け持つ3種類を見ます。

3-2. CLI が起動しない

(a) CLINotFoundError:Claude Code が見つからない(Python)

Python SDK は Claude Code CLI をサブプロセスとして起動します。claude の実行ファイルが見つからないと、接続が CLINotFoundError で失敗します。

Claude Code not found at: /your/configured/path

ClaudeAgentOptions の cli_path で存在しないファイルを指していた場合、メッセージにはその設定したパスが入ります。cli_path を設定していない場合、SDK は PATH とよくあるインストール場所を探し、メッセージにはプラットフォーム向けのインストール手順が入ります。

直し方は3つです。

  • Claude Code が入っていなければ入れる(本体側のセットアップのページ)。
  • cli_path を設定しているなら、そのファイルが存在し、claude の実行ファイルであることを確かめる。
  • PATH で探させているなら、アプリケーションが動く環境で claude --version が動くことを確かめる。IDE やサービスマネージャーのように、シェルの外から起動されるプロセスは、多くの場合シェルとは違う PATH で動きます。

3つ目が第94回の「サービス起動時に CLI が見つからない」の Python 側の原因そのものです。ターミナルで試して動いても、それはターミナルの PATH での話で、サービスの PATH では確かめたことにならない、という点が要です。

TypeScript の場合

TypeScript SDK は、同梱のプラットフォーム用パッケージと、pathToClaudeCodeExecutable に設定したパスで CLI を探します。メッセージで分けます。

メッセージ原因直し方
Native CLI binary for <platform>-<arch> not found同梱のプラットフォーム用パッケージが無い。最も多いのは、インストールで任意依存(optional dependencies)が飛ばされたこと任意依存を飛ばさずに @anthropic-ai/claude-agent-sdk を入れ直すか、pathToClaudeCodeExecutable をネイティブインストールに向ける
Claude Code native binary not found at <path> または Claude Code executable not found at <path>. Is options.pathToClaudeCodeExecutable set?解決したパスにファイルが無いか、プロセスからアクセスできないファイルがそのパスにあり、プロセスがアクセスできることを確かめる

1行目には例外があります。bun build --compile で作った単一ファイルの実行ファイルでは、同じメッセージでも原因と直し方が違い、TypeScript リファレンスの「単一実行ファイルへのコンパイル」に回されています。

第94回の記事で筆者は、TypeScript で CLI が見つからない原因を「同梱のバイナリが任意依存として入る作りのため、ビルド設定で任意依存を省くと消える、と読める」と推測として書きました。このページの1行目で、同梱のバイナリがプラットフォーム用のパッケージとして入り、任意依存を飛ばすと無くなることが確かめられました。第78回で見た「npm ci --omit=optional ではバイナリが入らない」という例外も同じ話です。

(b) CLIConnectionError: Refusing to execute batch script(Windows・Python)

Windows で、Python SDK が使う CLI のパスが .bat か .cmd のバッチスクリプト(npm のインストールが作る claude.cmd のシムを含む)だと、接続が CLIConnectionError で失敗します。

Refusing to execute batch script 'C:\\Users\\you\\AppData\\Roaming\\npm\\claude.cmd': Windows runs .bat/.cmd files via cmd.exe, which can execute commands injected through CLI arguments, and no reliable escaping for cmd.exe exists. Use a native claude executable instead: install Claude Code natively (irm https://claude.ai/install.ps1 | iex), point ClaudeAgentOptions(cli_path=...) at a claude.exe, or install the claude-agent-sdk wheel for a platform that bundles claude.exe (e.g. Windows x64).

原文は、この拒否は意図的なセキュリティの強化で、インストールが壊れているわけではないと強調しています。理由はこうです。Windows はバッチスクリプトを cmd.exe /c の呼び出しに書き換えて実行し、cmd.exe は実行時にコマンドライン全体を解析し直します。そのため、引数の値に紛れ込ませたコマンドが実行されうるのです。メッセージ本文にも「cmd.exe に対する信頼できるエスケープの方法は存在しない」と書かれています。

SDK は CLI にプロンプトや設定を引数で渡すので、利用者が入力した文字列が引数に入る構成では、これがそのまま注入の経路になります。第95回で見た「エージェントが処理する内容に埋め込まれた指示」とは別の層の、起動の仕組みそのものにある注入の穴を SDK 側でふさいだということです(筆者の解釈)。

ほとんどの Windows 環境はこのエラーに当たりません。Windows x64 向けの claude-agent-sdk のホイールは claude.exe を同梱しており、SDK は「同梱の CLI」→「見つけたネイティブの claude.exe」→「バッチのシム」の順に選ぶからです。拒否が出るのは2つの場合です。

  • cli_path を .bat/.cmd(npm の claude.cmd など)に設定した場合。
  • インストールにネイティブの claude.exe が同梱されていない場合。たとえば ARM64 Windows でソースからインストールし、PATH 上の claude が npm のシムだけ、という場合。

直し方は、バッチスクリプトではなくネイティブの実行ファイルを SDK に渡すことです。

  • cli_path を設定しているなら、claude.exe に向けるか、オプションを外す。cli_path を設定している間は SDK が探索を飛ばすので、ネイティブ版を入れただけでは効きません。
  • PowerShell でネイティブ版を入れる:irm https://claude.ai/install.ps1 | iex
  • x64 Windows なら、claude.exe を同梱した claude-agent-sdk のホイールを入れる。

2つ目の場合(ARM64 Windows のソースインストール)は、第78回で見た「バイナリが同梱されない2つの例外」の1つ(pip のソース配布)に当たります(筆者の対応づけ)。

(c) CLIConnectionError: Failed to start Claude Code:見つかったが起動できない

SDK は解決したパスでファイルを見つけたが、起動できなかった場合です。Python はこれを CLIConnectionError として送出します。TypeScript は、SDK 独自のクラスを持たないエラーでメッセージの反復を失敗させます。メッセージごとの意味は次のとおりです。

メッセージSDK意味
Failed to start Claude Code: <detail>Python残りの部分は OS 自身のエラー
Claude Code executable at <path> exists but failed to launchTypeScript設定したパスのスクリプトを実行できない
Claude Code native binary at <path> exists but failed to launchTypeScriptバイナリを実行できない。libc についての提案がメッセージに付く
Failed to spawn Claude Code process: <detail>TypeScriptその他の起動の失敗

どちらの SDK でも、よくある原因は、解決したパスが実行できないもの(テキストファイル、ディレクトリ、実行権限の無いファイル)を指していることです。ネイティブバイナリのメッセージに付く libc の提案は、「考えられる原因の1つ」として読むよう原文は書いています。

直し方は3つです。

  • 設定したパスが claude の実行ファイルそのものを指し、実行権限があることを確かめる。
  • 独自のパスが要らないなら、Python の cli_path、TypeScript の pathToClaudeCodeExecutable を外し、SDK に自分で探させる(同梱のコピーが優先される)。
  • 失敗しているのがコンテナイメージの中の同梱バイナリなら、イメージのビルド中に SDK を入れ直して、同梱のバイナリがコンテナのプラットフォームと合うようにするか、動かすアーキテクチャ用にイメージを作り直す。よくある原因は、コンテナのアーキテクチャや libc と合わないバイナリか、イメージのビルド中に実行権限を失ったバイナリです。

3つ目は第94回の「イメージに CLI はあるが起動しない」と同じ内容です。たとえば Apple シリコンの Mac で node_modules を作ってから x86_64 のイメージにコピーすると、同梱のバイナリは Mac 用のままになる、という類の事故がこれに当たると考えられます(例は筆者のもの)。イメージの中で入れ直すのが確実だ、というのが原文の勧めです。

(d) CLIConnectionError: Not connected(Python の ClaudeSDKClient)

Python で ClaudeSDKClient のメソッドを、接続する前、または切断した後に呼ぶと、次のメッセージで CLIConnectionError が出ます。

Not connected. Call connect() first.

原文の指示は「メッセージのとおりにする」です。他のメソッドの前に await client.connect() を呼ぶか、async with ClaudeSDKClient() as client: でクライアントを開きます。後者は入り口で接続してくれます。

3-3. CLI のプロセスが途中で終わった

この節のエラーは、アプリケーションが使っている間に Claude Code のプロセスが終了したことを意味します。どのエラーになるかは、SDK の言語と、CLI が終了する前にエラー結果を報告したかどうかで変わります。

CLI がエラー結果を報告せずに終了CLI がエラー結果を報告してから終了
PythonProcessErrorResultError(ProcessError のサブクラス)。data 属性に完全なエラー結果
TypeScript単純な Error(Claude Code process exited with code N)単純な Error(メッセージは Claude Code returned an error result: で始まる)

表は原文の3つの節を筆者がまとめたものです。

(a) ProcessError(Python)

Python SDK は、Claude Code のプロセスが0以外のコードで終わると ProcessError を送出します。

Command failed with exit code 1 (exit code: 1)
Error output: Check stderr output for details

ここに落とし穴があります。メッセージは終了コードを2回言うだけで、Error output の行は固定の文字列であり、プロセスの実際のエラー出力ではありません。 例外の stderr 属性にも同じ固定の文字列が入ります。終了コードは exit_code 属性にあります。CLI が実際に標準エラーに書いた内容を取るには、ClaudeAgentOptions で stderr コールバックを渡し、受け取ったものを記録します。

つまり、例外の stderr 属性を読んでログに残す実装は、何も残していないのと同じです。stderr コールバックは、問題が起きてから付けるのではなく、本番では最初から付けておくべきものだと言えます(筆者の指摘)。第96回の CLAUDE_CODE_OTEL_DIAG_STDERR=1 の診断も、同じコールバックで読むものでした。

単純な ProcessError は、CLI がエラー結果を報告せずに終わったことを意味します。CLI がエラー結果を報告していた場合は、代わりに ResultError が出ます((c) で説明)。ResultError は ProcessError のサブクラスなので、except ProcessError は両方を捕まえます。分けて扱いたいなら、except ResultError の節を先に置きます。

(b) Claude Code process exited with code N(TypeScript)

このメッセージは IDE のラッパーも出しますが、VS Code などのランチャーの場合は本体側のエラーリファレンスの担当です。ここでは TypeScript SDK のコードが受け取るものを扱います。SDK は0以外の CLI の終了を単純な Error として出し、query() のメッセージを回す for await のループを失敗させます。捕まえる SDK のエラークラスが無いので、ループを try/catch で包み、メッセージの文面で判定します。

Claude Code process exited with code 1. stderr: <tail of the CLI's stderr>

CLI が標準エラーに何か書いていれば、メッセージはその末尾で終わります。全体を取るには、クエリのオプションで stderr コールバックを渡します。シグナルで強制終了されたプロセスは、同じ形で Claude Code process terminated by signal <name> と報告します。

TypeScript で文面に頼って分岐する実装は、SDK の更新で文言が変わると静かに外れる可能性があります。判定に使う文字列は先頭の決まった部分(Claude Code process exited with code など)に絞り、どれにも当たらない場合の扱いを必ず用意しておくのが安全です(筆者の指摘)。

(c) Claude Code returned an error result(両方)

CLI が終了する前にエラー結果を報告した場合、両方の SDK は、プロセス終了のエラーをこのメッセージに置き換えます。

Claude Code returned an error result: <the CLI's own error report>

コロンの後ろは、何がまずかったかについての CLI 自身の報告です。原文は、終了したこと自体ではなく、そこから調べ始めるよう書いています。Python はこれを ResultError として送出し、その data 属性に完全なエラー結果が入ります。TypeScript は同じ形のメッセージを持つ単純な Error でループを失敗させます。

第77回で見た結果メッセージの subtype(error_max_turns など)や、第94回の「maxTurns に達すると error_max_turns の結果の後に query() が例外を出す」は、この経路に当たると読めます(筆者の対応づけ)。1回きりの query() では、エラーの結果メッセージがまずストリームに流れ、その後でこの例外が来る、という順番です。

3-4. 構造化出力:成功なのに structured_output が空

結果メッセージが subtype: "success" で終わっているのに、Python では structured_output が None、TypeScript では undefined になることがあります。実行は完了していますが、検証に通った出力は存在しません。ここに至る道の1つは、出力が満たしようのないスキーマです(例:互いに矛盾する長さの制約)。

原文の指示は3つです。

  • アプリケーションのコードでは、この結果を失敗として扱う。
  • structured_output を使う前に、subtype が success であることと、structured_output が存在することの両方を確かめる(構造化出力のページの「エラーハンドリング」に、両方の SDK でのこの書き方がある)。
  • 正しいはずのスキーマで繰り返し起きるなら、スキーマが満たせるものかを確かめ、検証に通るまで単純化し、制約を1つずつ戻していく。

subtype だけを見て成功と判断すると、ここで落ちます。「成功」は「実行が最後まで行った」という意味で、「欲しい形のデータが得られた」とは別、ということです(筆者の整理)。


4. まとめ

  • このページが受け持つのは CLI の起動・CLI プロセスの終了・構造化出力の3種類。フック・スキル・MCP・プラグイン・サブエージェント・チェックポイントは各ページ、認証や API のエラーはエラーリファレンス、デプロイ失敗はホスティングのページへ。
  • CLINotFoundError:Python は cli_path と PATH。サービスの PATH で claude --version を確かめる。TypeScript の Native CLI binary for <platform>-<arch> not found は任意依存を飛ばしたのが最多の原因。
  • Windows のバッチスクリプト拒否は意図的な対策(cmd.exe の再解析で引数から注入できる)。ネイティブの claude.exe を使う。cli_path を設定している間は探索されない。
  • Failed to start 系は、パスが実行できないもの・アーキテクチャや libc の不一致・実行権限の喪失。コンテナならイメージの中で SDK を入れ直す。
  • Not connected は connect() か async with で。
  • Python の ProcessError の Error output は固定文字列。本当の出力は stderr コールバックで取る。ResultError は ProcessError のサブクラスなので先に捕まえる。
  • TypeScript は SDK のエラークラスが無く、単純な Error の文面で判定する。
  • Claude Code returned an error result: の後ろが CLI 自身の報告。そこから調べる。
  • success でも structured_output が空なら失敗扱い。subtype と存在の両方を確かめる。
  • 未掲載のエラーは、エラーの全文と SDK のバージョンを添えて SDK のリポジトリに報告する。

次回予告

次回は 「構造化出力」(agent-sdk/structured-outputs) を取り上げる予定です。

今回、結果が「成功」でも構造化出力が空のことがあり、subtype と出力の有無を両方確かめるよう求められていました。次回は、そもそもエージェントに JSON スキーマに沿った形で結果を返させるにはどうするのか、スキーマの渡し方、検証の仕組み、そして今回触れたエラーハンドリングの具体的な書き方を見ていきます。


よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました