Chapter 13

Handle Approvals and User Input

Surface Claude's approval requests and clarifying questions to users, then return their decisions to the SDK.

While working on a task, Claude sometimes needs to check in with users. It might need permission before deleting files, or need to ask which database to use for a new project. Your application needs to surface these requests to users so Claude can continue with their input.

Claude requests user input in two situations: when it needs permission to use a tool (like deleting files or running commands), and when it has clarifying questions (via the AskUserQuestion tool). Both trigger your canUseTool callback, which pauses execution until you return a response.

For clarifying questions, Claude generates the questions and options. Your role is to present them to users and return their selections. You can't add your own questions to this flow; if you need to ask users something yourself, do that separately in your application logic.

Detect when Claude needs input

Pass a canUseTool callback in your query options. The callback fires whenever Claude needs user input, receiving the tool name and input as arguments:

detect-input.ts
async function handleToolRequest(toolName, input) {
  // Prompt user and return allow or deny
}

const options = { canUseTool: handleToolRequest }

The callback fires in two cases:

  1. Tool needs approval: Claude wants to use a tool that isn't auto-approved by permission rules or modes. Check toolName for the tool (e.g., "Bash", "Write").
  2. Claude asks a question: Claude calls the AskUserQuestion tool. Check if toolName == "AskUserQuestion" to handle it differently.

Your callback must return within 60 seconds or Claude will assume the request was denied and try a different approach.

Handle tool approval requests

Once you've passed a canUseTool callback, it fires when Claude wants to use a tool that isn't auto-approved. Your callback receives:

ArgumentDescription
toolNameThe name of the tool Claude wants to use (e.g., "Bash", "Write", "Edit")
inputThe parameters Claude is passing to the tool. Contents vary by tool.

Common input fields by tool:

ToolInput fields
Bashcommand, description, timeout
Writefile_path, content
Editfile_path, old_string, new_string
Readfile_path, offset, limit
tool-approval-example.ts
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) => {
      // Display the tool request
      console.log(`Tool: ${toolName}`);
      if (toolName === "Bash") {
        console.log(`Command: ${input.command}`);
      }

      // Get user approval
      const response = await prompt("Allow this action? (y/n): ");

      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);
}

Respond to tool requests

Your callback returns one of two response types:

ResponseTypeScript
Allow{ behavior: "allow", updatedInput }
Deny{ behavior: "deny", message }

When allowing, pass the tool input (original or modified). When denying, provide a message explaining why. Claude sees this message and may adjust its approach.

Approve

Let the tool execute as Claude requested.

return { behavior: "allow", updatedInput: input };

Approve with changes

Modify the input before execution (e.g., sanitize paths, add constraints).

const sandboxedInput = {
  ...input,
  command: input.command.replace("/tmp", "/tmp/sandbox")
};
return { behavior: "allow", updatedInput: sandboxedInput };

Reject

Block the tool and tell Claude why.

return { behavior: "deny", message: "User rejected this action" };

Suggest alternative

Block but guide Claude toward what the user wants instead.

return {
  behavior: "deny",
  message: "User doesn't want to delete files. " +
    "They asked if you could compress them into an archive instead."
};

Handle clarifying questions

When Claude needs more direction on a task with multiple valid approaches, it calls the AskUserQuestion tool. This triggers your canUseTool callback with toolName set to AskUserQuestion. The input contains Claude's questions as multiple-choice options, which you display to the user and return their selections.

Tip

Clarifying questions are especially common in plan mode, where Claude explores the codebase and asks questions before proposing a plan. This makes plan mode ideal for interactive workflows where you want Claude to gather requirements before making changes.

1

Detect AskUserQuestion

Check if toolName === "AskUserQuestion" to handle it differently from other tools

2

Parse the question input

The input contains a questions array with question, options, and multiSelect fields

3

Collect answers from the user

Present the questions to your users and collect their selections

4

Return answers to Claude

Build an answers object mapping each question to the selected label

Question format

The input contains Claude's generated questions in a questions array. Each question has these fields:

FieldDescription
questionThe full question text to display
headerShort label for the question (max 12 characters)
optionsArray of 2-4 choices, each with label and description
multiSelectIf true, users can select multiple options
{
  "questions": [
    {
      "question": "How should I format the output?",
      "header": "Format",
      "options": [
        { "label": "Summary", "description": "Brief overview" },
        { "label": "Detailed", "description": "Full explanation" }
      ],
      "multiSelect": false
    }
  ]
}

Response format

Return an answers object mapping each question's question field to the selected option's label:

return {
  behavior: "allow",
  updatedInput: {
    questions: input.questions,
    answers: {
      "How should I format the output?": "Summary",
      "Which sections should I include?": "Introduction, Conclusion"
    }
  }
};

For multi-select questions, join multiple labels with ", ". For free-text input, use the user's custom text directly.

Complete example

This example handles clarifying questions in a terminal application. Claude asks questions when it needs user input to proceed, such as when deciding on a tech stack for a mobile app.

clarifying-questions.ts
import { query } from "@anthropic-ai/claude-agent-sdk";

// Parse user input as option number(s) or free text
function parseResponse(response: string, options: any[]): string {
  const indices = response.split(",").map((s) => parseInt(s.trim()) - 1);
  const labels = indices
    .filter((i) => !isNaN(i) && i >= 0 && i < options.length)
    .map((i) => options[i].label);
  return labels.length > 0 ? labels.join(", ") : response;
}

// Display questions and collect answers
async function handleAskUserQuestion(input: any) {
  const answers: Record<string, string> = {};

  for (const q of input.questions) {
    console.log(`
${q.header}: ${q.question}`);
    q.options.forEach((opt, i) => {
      console.log(`  ${i + 1}. ${opt.label} - ${opt.description}`);
    });

    const response = (await prompt("Your choice: ")).trim();
    answers[q.question] = parseResponse(response, q.options);
  }

  return {
    behavior: "allow",
    updatedInput: { questions: input.questions, answers }
  };
}

Limitations

  • -60-second timeout: canUseTool callbacks must return within 60 seconds or Claude will retry with a different approach
  • -Subagents: AskUserQuestion is not currently available in subagents spawned via the Task tool
  • -Question limits: each AskUserQuestion call supports 1-4 questions with 2-4 options each

Other ways to get user input

The canUseTool callback and AskUserQuestion tool cover most approval and clarification scenarios, but the SDK offers other ways to get input:

Streaming input

Use when you need to:

  • Interrupt the agent mid-task
  • Provide additional context without waiting for Claude to ask
  • Build chat interfaces with follow-up messages

Custom tools

Use when you need to:

  • Collect structured input beyond multiple-choice
  • Integrate external approval systems
  • Implement domain-specific interactions

Related resources

  • -Configure permissions: set up permission modes and rules
  • -Control execution with hooks: run custom code at key points in the agent lifecycle