Claude Agent SDK のエージェントは、作業の途中で「このコマンドを実行してよいか」「どちらの方針にするか」をユーザーに確認することがあります。この記事では公式ドキュメント「Handle approvals and user input」に沿って、canUseTool コールバックでツール承認と AskUserQuestion の質問を受け取り、許可・拒否・入力の書き換え・回答を返す方法を整理します。SDK の基本は「Claude Agent SDK でエージェントを構築する」を参照してください。
承認と質問が届くタイミング
Claude Agent SDK でエージェントを動かしていると、Claude が作業の途中でユーザーに確認したくなる場面があります。ファイルの削除やコマンド実行の前に許可がほしいとき、あるいは「どのデータベースを使うか」のような方針を尋ねたいときです。この2つはどちらも canUseTool コールバックに届き、コールバックが値を返すまで実行は一時停止します。通常の会話ターンのように Claude が応答を終えて次の入力を待つ状態とは異なります。
質問の内容と選択肢は Claude が生成します。アプリ側の役割はそれらを画面に出して、ユーザーの選択を返すことです。アプリ独自の質問をこの流れに差し込むことはできません。コールバックは無期限に待たせられますが、ユーザーの返事がプロセスの寿命より長くかかりそうなら、PreToolUse フックから defer を返してプロセスを終了し、保存済みセッションから再開する方法が公式に案内されています。
コールバックの登録は、クエリのオプションに canUseTool(Python では can_use_tool)を渡すだけです。
from claude_agent_sdk import ClaudeAgentOptions
async def handle_tool_request(tool_name, input_data, context):
# ユーザーに確認して allow か deny を返す
...
options = ClaudeAgentOptions(can_use_tool=handle_tool_request)
import type { CanUseTool } from "@anthropic-ai/claude-agent-sdk";
const handleToolRequest: CanUseTool = async (toolName, input, options) => {
// options には { signal: AbortSignal, suggestions?: PermissionUpdate[] } が入る
return { behavior: "deny", message: "User declined" };
};
const options = { canUseTool: handleToolRequest };
コールバックが呼ばれるのは次の2つの場合です。
- ツールの承認: 許可ルールや許可モードで自動承認されていないツールを Claude が使おうとしたとき。
tool_nameでBashやWriteなどを見分けます。 - 質問: Claude が
AskUserQuestionツールを呼んだとき。tools配列で使えるツールを絞っている場合は、AskUserQuestionも含めてください。
重要な注意点として、自動承認されたツールではコールバックは呼ばれません。許可ルールや acceptEdits、bypassPermissions のようなモードが先に許可を出すと、canUseTool まで到達しないためです。すべてのツール呼び出しに必ず適用したい処理は、権限判定より前に動く PreToolUse フックで実装します。承認待ちを Slack やメールなどで通知したいときは PermissionRequest フックが使えます。権限判定の順序は「Agent SDK の権限設定」、フックは「Agent SDK のフック」で扱っています。
ツール承認リクエストへの応答
コールバックは3つの引数を受け取ります。
| 引数 | 内容 |
|---|---|
toolName | Claude が使おうとしているツール名(Bash、Write、Edit など) |
input | Claude がツールに渡すパラメータ。内容はツールごとに異なる |
options(TS)/ context(Python) | 再確認を避けるための suggestions(PermissionUpdate の候補)と中断用シグナル。TypeScript の signal は AbortSignal、Python のシグナル欄は将来用の予約 |
input の代表的なフィールドは、Bash が command・description・timeout、Write が file_path・content、Edit が file_path・old_string・new_string、Read が file_path・offset・limit です。これらを表示してユーザーに判断してもらい、次のいずれかを返します。
| 応答 | Python | TypeScript |
|---|---|---|
| 許可 | PermissionResultAllow(updated_input=...) | { behavior: "allow", updatedInput } |
| 拒否 | PermissionResultDeny(message=...) | { behavior: "deny", message } |
許可するときは、updatedInput(Python では updated_input)に別の入力を返さない限り、Claude が要求した入力のままツールが実行されます。公式ドキュメントによれば、v2.1.207 より前の Claude Code は updatedInput を省いた許可結果を検証エラーで拒否していました。拒否するときの message は Claude に見えるので、理由を書けば Claude が別の方法を試せます。
次は、ターミナルで y/n を尋ねる最小の例です。Python ではストリーム入力を使い、公式が「必須の回避策」と書いているダミーの PreToolUse フックでストリームを開いたままにしています。
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Create a test file in /tmp and then delete it",
options: {
canUseTool: async (toolName, input) => {
console.log(`Tool: ${toolName}`);
const response = await prompt("Allow this action? (y/n): "); // prompt はアプリ側の入力関数
if (response.toLowerCase() === "y") {
return { behavior: "allow", updatedInput: input };
}
return { behavior: "deny", message: "User denied this action" };
}
}
})) {
if ("result" in message) console.log(message.result);
}
返し方のバリエーション
- 承認: 入力をそのまま返し、要求どおりに実行させる
- 変更つき承認: 入力を書き換えて返す(パスの正規化や制約の追加など)。Claude には書き換えたことは伝わらず、結果だけが見える
- 承認して記憶:
suggestionsのうちdestinationがlocalSettingsのものをupdatedPermissionsに入れて返すと、ルールが.claude/settings.local.jsonに書かれ、以降の同種の呼び出しでは確認が出なくなる。Python 例はclaude-agent-sdk0.1.80 以降が必要 - 拒否:
messageに理由を書いてツールを止める - 代替案の提示: 拒否しつつ、たとえば「削除ではなくアーカイブに圧縮して」とメッセージで誘導する
- 方向転換: 大きく指示を変えたいときは、ストリーミング入力で新しい指示を直接送る
canUseTool: async (toolName, input, { suggestions = [] }) => {
const choice = await askUser(`Allow ${toolName}?`, ["once", "always", "no"]);
if (choice === "always") {
const persist = suggestions.filter((s) => s.destination === "localSettings");
return { behavior: "allow", updatedInput: input, updatedPermissions: persist };
}
if (choice === "once") {
return { behavior: "allow", updatedInput: input };
}
return { behavior: "deny", message: "User declined" };
};
TypeScript では、オプションに suppressAlwaysAllowRule: true が付いている要求に対して「常に許可」の選択肢を出さないようにします。このヒントは Agent SDK v0.3.268 以降で使え、Python の context には含まれません。なお askUser は、アプリ独自の確認 UI を表す仮の関数です。
AskUserQuestion の質問に答える
複数の妥当なやり方がある作業で方針が必要になると、Claude は AskUserQuestion ツールを呼びます。toolName が AskUserQuestion のときに質問用の処理へ分岐させます。公式ドキュメントによれば、この質問は特に plan モードで多く、Claude がコードベースを調べて要件を確かめてから計画を出す対話的な流れに向いています。手順は次のとおりです。
- コールバックを渡す。
toolsで機能を絞る場合はAskUserQuestionを含める(例:["Read", "Glob", "Grep", "AskUserQuestion"]) - コールバック内で
toolName === "AskUserQuestion"を判定し、専用の処理へ回す input.questionsを読む- 質問と選択肢を画面に出し、ユーザーの選択を集める
answersを組み立てて返す
質問の形式
| フィールド | 内容 |
|---|---|
question | 表示する質問文の全体 |
header | 質問の短いラベル(最大12文字) |
options | 2〜4個の選択肢。各項目に label と description。TypeScript では任意で preview |
multiSelect | true なら複数選択できる |
{
"questions": [
{
"question": "How should I format the output?",
"header": "Format",
"options": [
{ "label": "Summary", "description": "Brief overview of key points" },
{ "label": "Detailed", "description": "Full explanation with examples" }
],
"multiSelect": false
}
]
}
回答の返し方
返す updatedInput には、元の questions 配列をそのまま渡し(ツール処理に必須)、answers に「question の文字列 → 選ばれた選択肢の label」を入れます。複数選択では、ラベルの配列を渡すか、", " で連結した文字列を渡します。
return {
behavior: "allow",
updatedInput: {
questions: input.questions,
answers: {
"How should I format the output?": "Summary",
"Which sections should I include?": "Introduction, Conclusion"
}
}
};
ユーザーが選択肢に合う答えを持たない場合に備え、Claude の選択肢の後ろに「その他」を用意して自由入力を受け付けると親切です。そのときは「Other」という語ではなく、ユーザーが入力したテキストを回答値にします。質問カードを閉じて全体への返信を直接書かせる UI なら、任意の response フィールドを設定できます。response がある場合、Claude には個別の回答一覧ではなく「ユーザーはこう返答した」という内容が渡されます。
選択肢のプレビュー(TypeScript)
toolConfig.askUserQuestion.previewFormat を設定すると、各選択肢に preview が付き、レイアウトや配色などを視覚的に見せられます。未設定なら preview は生成されません。"markdown" は ASCII アートとコードブロック、"html" はスタイル付きの <div> 断片で、SDK は <script>・<style>・<!DOCTYPE> をコールバックに届く前に拒否します。設定はセッション内のすべての質問に効き、Claude は視覚比較が役立つ選択肢にだけ付けるため、描画前に undefined の確認が必要です。
制限事項と他の入力手段
公式ドキュメントが挙げている制限は2点です。
- サブエージェント: Agent ツールで起動したサブエージェントでは、現時点で
AskUserQuestionは使えません - 質問数: 1回の
AskUserQuestionは1〜4問、各問に2〜4個の選択肢です
承認や質問の枠に収まらない入力が必要なときは、別の手段を使います。ストリーミング入力は、作業中の中断・方向転換、先回りの補足情報の送信、長時間処理中の追加メッセージを受けるチャット UI に向いています。カスタムツールは、複数段のフォームや既存の承認・チケットシステムとの連携など、選択式を超えるやり取りに使えます(Agent SDK のカスタムツール)。
本ページは Handle approvals and user input を中心に、Configure permissions と Hooks の記述を踏まえた非公式の日本語解説です(確認日 2026-10-25)。コード例は一部を簡略化しています。最新の仕様は公式ドキュメントを確認してください。