Claude Media
Agent SDK canUseToolで承認と確認質問を処理する

Agent SDK canUseToolで承認と確認質問を処理する

Agent SDKのcanUseToolコールバックでツール承認リクエストとAskUserQuestionの確認質問をアプリ側から処理する方法を解説します。

canUseToolコールバックは何のために存在するか

タスクを進める途中で、Claudeはユーザーへ確認したい場面に出会います。ファイルを削除する前に許可が欲しい場合や、新規プロジェクトでどのデータベースを使うか決めたい場合です。アプリケーション側はこうしたリクエストをユーザーへ見せ、返ってきた回答をClaudeへ渡す必要があります。

Claudeがユーザー入力を求めるのは2つの場面です。ツールを使う許可が必要なとき(ファイル削除やコマンド実行など)と、AskUserQuestionツールを通じて確認質問があるときです。どちらもcanUseToolコールバックを発火させ、コールバックが応答を返すまで実行は一時停止します。これは、Claudeが応答を終えて次のメッセージを待つ通常の会話ターンとは異なる挙動です。

確認質問については、質問と選択肢を作るのはClaudeの役割です。アプリ側の役割はそれをユーザーへ提示し、選ばれた回答を返すことに限られます。このフローに自分から質問を追加することはできないので、アプリ独自に何かをユーザーへ尋ねたい場合は、別途アプリケーションのロジックとして実装します。

コールバックは無期限に保留され続けられます。実行はコールバックが値を返すまで一時停止したままで、SDKがその待機をキャンセルするのはクエリ自体がキャンセルされたときだけです。ユーザーの応答がプロセスを起動し続けられる時間より長くかかりそうな場合は、コールバックで待つ代わりにPreToolUseフックでdefer判定を返し、プロセスを終了させて永続化済みのセッションから後で再開する設計にします。

canUseToolを登録してツール承認を検出する

クエリのオプションにcanUseToolコールバックを渡すと、Claudeがユーザー入力を必要とするたびに、ツール名と入力を引数にして発火します。

async function handleToolRequest(toolName, input, options) {
  // optionsには { signal: AbortSignal, suggestions?: PermissionUpdate[] } が含まれる
  // ユーザーへ提示し、allowかdenyを返す
}
 
const options = { canUseTool: handleToolRequest };

コールバックが発火するのは2つのケースです。1つ目は、権限ルールや権限モードで自動承認されないツールをClaudeが使おうとするときで、tool_name"Bash""Write"のように渡ってきます。2つ目は、ClaudeがAskUserQuestionツールを呼び出すときで、tool_name"AskUserQuestion"かどうかで処理を分けます。ツール一覧をtools配列で絞り込んでいる場合は、AskUserQuestionを含めておかないと確認質問自体ができなくなります。

自動承認されたツールに対して、このコールバックは絶対に発火しません。許可ルールやacceptEditsbypassPermissionsのようなモードによる承認は、canUseToolが参照される前に解決済みになります。allowed_toolsに素のツール名を並べているだけの場合も、評価フローがask系のルールやplanモードでプロンプトへ差し戻したときにだけコールバックが実行されます。

すべてのツール呼び出しに一様に適用したいロジックがあるなら、評価フローより先に実行され許可・拒否・変更ができるPreToolUseフックを使います。承認待ちの間にSlackやメール、プッシュ通知を外部へ送って人間の判断を仰ぎたい場合はPermissionRequestフックが使えます。

ツール承認リクエストをハンドリングする

コールバックはtoolNameinputoptions(Pythonではcontext)の3引数を受け取ります。inputにはツールごとのパラメータが入り、BashならcommanddescriptiontimeoutWriteならfile_pathcontentEditならfile_pathold_stringnew_stringReadならfile_pathoffsetlimitが代表的な例です。この情報をユーザーへ見せて、許可するか拒否するかの判断材料にします。

async def can_use_tool(tool_name, input_data, context):
    print(f"Tool: {tool_name}")
    if tool_name == "Bash":
        print(f"Command: {input_data.get('command')}")
    response = input("Allow this action? (y/n): ")
 
    if response.lower() == "y":
        return PermissionResultAllow(updated_input=input_data)
    else:
        return PermissionResultDeny(message="User denied this action")

コールバックが返す応答は、許可(PermissionResultAllow/{ behavior: "allow", updatedInput })か拒否(PermissionResultDeny/{ behavior: "deny", message })のいずれかです。許可するときにClaudeが要求した入力をそのまま使うのではなく、updatedInput(Python: updated_input)を変更して渡すこともできます。v2.1.207より前のClaude Codeでは、updatedInputを省略した許可応答はバリデーションエラーとして拒否されていました。拒否するときは理由をmessageに入れます。Claudeはそのメッセージを見て、別のアプローチに切り替えることがあります。

Pythonでcan_use_toolを使うにはストリーミングモードが前提になります。有限のメッセージジェネレータをquery(prompt=generator)のように渡すと、SDKは最後のメッセージの後で入力ストリームを閉じてしまい、登録済みのフックが開いたままにしていない限り権限コールバックの前にストリームが閉じます。回避策としては、{"continue_": True}を返すだけのダミーのPreToolUseフックを登録してストリームを開いたままにする方法があります。ClaudeSDKClient.connect()をプロンプトなしで呼び、ClaudeSDKClient.query()でメッセージを送る形にすれば、ストリームは自動的に開いたままになりこの回避策は不要になります。ストリーミングと単発クエリの違いはストリーミング入力とシングルメッセージ入力の使い分けで扱っています。

5つの応答パターンを使い分ける

許可・拒否の2択にとどまらず、入力を書き換えたりClaudeへ文脈を渡したりする応答パターンが用意されています。

パターン何をするか
承認何をするかClaudeが要求したinputをそのまま渡して実行する
変更して承認何をするかパスのサニタイズや制約の追加など、実行前にinputを書き換える
承認して記憶する何をするかコールバックの第3引数suggestionsからPermissionUpdateを1つ選び、許可応答のupdatedPermissionsに載せて返すことで、同種の呼び出しを次回以降スキップさせる
拒否何をするかツールをブロックし、理由をClaudeへ伝える
代案を提示して拒否何をするかブロックしつつメッセージへ誘導を書き、Claudeに別の進め方を促す

「承認して記憶する」パターンでは、suggestionsから選んだPermissionUpdateを許可応答のupdatedPermissionsにそのまま載せて返します。destinationlocalSettingsPermissionUpdateであれば、そのルールが.claude/settings.local.jsonへ書き込まれ、以降のセッションで一致する呼び出しのプロンプトがスキップされるようになります。公式のPythonサンプルはclaude-agent-sdk 0.1.80以降を前提にしています。

これらとは別に、コールバックの応答という枠を超えて、ストリーミング入力で新しい指示をClaudeへ直接送り、現在のツールリクエストごと方向転換させる「完全なリダイレクト」という手段もあります。

AskUserQuestionで確認質問を処理する

複数の妥当な進め方があるタスクで方向性を絞りたいとき、ClaudeはAskUserQuestionツールを呼び出します。tool_nameAskUserQuestionのときは、他のツールとは別扱いにして質問を処理します。planモードで特に頻出するパターンで、Claudeがコードベースを調べながら計画を提案する前に質問することがよくあります。

入力にはquestions配列が入り、各要素は表示するquestionテキスト、最大12文字のheader、2〜4件の選択肢を持つoptions(それぞれlabeldescription)、複数選択を許すかのmultiSelectを持ちます。

{
  "questions": [
    {
      "question": "How should I format the output?",
      "header": "Format",
      "options": [
        { "label": "Summary", "description": "Brief overview" },
        { "label": "Detailed", "description": "Full explanation" }
      ],
      "multiSelect": false
    }
  ]
}

応答はanswersオブジェクトとして返します。キーは質問のquestionテキストそのもの、値は選ばれた選択肢のlabelです。複数選択の質問ではラベルの配列か", "で結合した文字列を渡します。ユーザーが構造化された質問カード自体を閉じて自由な返信を書けるようにしている場合だけ、responseフィールドに自由記述の返信を入れます。responseを設定すると、Claudeは各質問への回答一覧の代わりに「ユーザーはこう返信しました」というメッセージを受け取ります。

TypeScript版では、toolConfig.askUserQuestion.previewFormatを設定すると各選択肢にpreviewフィールドが追加され、レイアウトや配色のような視覚的な比較が要る選択肢にはASCIIアートやHTML断片のプレビューが付きます。設定しない場合、Claudeはプレビューを生成せずこのフィールド自体が存在しません。"html"を選んだ場合、SDK側は<script><style><!DOCTYPE>をコールバックへ渡る前に取り除いたうえで渡します。

自由記述の回答を受け付ける

Claudeが用意した選択肢がユーザーの意図をカバーしないことは珍しくありません。選択肢の後に自由入力を受け付ける「その他」を追加で表示し、ユーザーが打ち込んだテキストをそのままanswersの値として使います。「Other」という単語自体を値にしないことが重要です。番号入力と自由記述の両方を受け付ける実装では、入力が数値として解釈できるかどうかで選択肢のラベルを使うか、テキストそのものを使うかを振り分けます。

よくあるつまずき

サブエージェントではAskUserQuestionが使えません。Agentツール経由で起動したサブエージェントの中からは、このツールを呼べません。確認質問が必要な処理は、メインのエージェントセッション側に持たせる設計にします。

質問数と選択肢数には上限がありますAskUserQuestionの1回の呼び出しは1〜4問、各質問の選択肢は2〜4件までです。それ以上の分岐が必要な場合は、質問をいくつかの段階に分けて複数回に分けるか、カスタムツールでフォームやウィザードのような独自のインタラクションを実装します。

Pythonではストリーム維持を忘れると承認フローが動きません。前述のとおり、有限のメッセージジェネレータだけを渡すとストリームが早期に閉じ、can_use_toolが呼ばれる前にセッションが終わってしまいます。ダミーのPreToolUseフックを挟むか、ClaudeSDKClient.connect()をプロンプトなしで使う構成に変えます。

すべての呼び出しに効くロジックは、コールバックではなくフックに書きますcanUseToolは自動承認された呼び出しには一切発火しないため、監査ログのようにツール呼び出し全体へ一様に適用したい処理をcanUseToolだけに書くと漏れが生じます。評価フローより先に走るPreToolUseフックへ移すのが確実です。

まとめ

canUseToolコールバックは、ツール承認とAskUserQuestionによる確認質問という2種類のユーザー入力要求を、同じ仕組みで受け止めます。単純な許可・拒否だけでなく、入力の書き換え・ルールの記憶・代案の提示・完全なリダイレクトまで応答の幅を広げられる点が、通常の会話ターンとは違うこの仕組みの価値です。実装するときは、自動承認された呼び出しにはコールバックが発火しないこと、Pythonではストリーミングを維持する工夫が要ること、サブエージェント内ではAskUserQuestionが使えないことの3点を先に押さえておくと、実装の手戻りを避けられます。

権限モードと評価フローの全体像はClaude Code Planモード完全ガイドで、PreToolUseフックの実例はClaude Code Hooks実例カタログで補えます。ストリーミング入力とシングルメッセージ入力の違いはストリーミング入力とシングルメッセージ入力の使い分けにまとめています。

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