---
title: "Handle Approvals and User Input"
aliases: [canUseTool, user-approvals, ask-user-question]
tags: [agent-sdk, permissions, user-input, python, typescript]
sources: [raw/Handle approvals and user input.md]
created: 2026-04-17
updated: 2026-04-17
---

# Handle Approvals and User Input

Surface Claude's approval requests and clarifying questions to users, then return their decisions back to the SDK via the `canUseTool` callback.

## When Claude Pauses for Input

Claude stops and fires `canUseTool` in two situations:

| Situation | `tool_name` value | Trigger |
|-----------|------------------|---------|
| Tool needs permission | `"Bash"`, `"Write"`, `"Edit"`, etc. | Tool not auto-approved by [[wiki/agent-sdk/configure-permissions\|permission rules]] |
| Clarifying question | `"AskUserQuestion"` | Claude needs direction before proceeding |

This is distinct from a normal conversation turn — execution is paused until your callback returns.

## canUseTool Callback Setup

```python
async def handle_tool_request(tool_name, input_data, context):
    # prompt user, return allow or deny
    ...

options = ClaudeAgentOptions(can_use_tool=handle_tool_request)
```

**Python requirement:** streaming mode + a `PreToolUse` hook returning `{"continue_": True}` — without it, the stream closes before the callback fires.

```python
async def dummy_hook(input_data, tool_use_id, context):
    return {"continue_": True}

options = ClaudeAgentOptions(
    can_use_tool=can_use_tool,
    hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
)
```

## Tool Approval Requests

### Callback arguments

| Argument | Description |
|----------|-------------|
| `tool_name` | Tool Claude wants to use (`"Bash"`, `"Write"`, `"Edit"`, `"Read"`) |
| `input_data` | Tool-specific parameters (e.g. `command`, `file_path`, `content`) |
| `context` | Suggestions (`PermissionUpdate` entries) + cancellation signal |

### Response types

| Response | Python | TypeScript |
|----------|--------|------------|
| Allow | `PermissionResultAllow(updated_input=input_data)` | `{ behavior: "allow", updatedInput }` |
| Deny | `PermissionResultDeny(message="reason")` | `{ behavior: "deny", message }` |

### Response strategies

- **Approve** — pass input unchanged
- **Approve with changes** — sanitize paths, add constraints before passing
- **Reject** — block the tool, Claude sees your message and may adjust
- **Suggest alternative** — block + guide Claude toward what user wants
- **Redirect** — use streaming input to send a new instruction entirely

## AskUserQuestion — Clarifying Questions

When Claude needs direction (common in [[wiki/agent-sdk/configure-permissions\|plan mode]]), it calls `AskUserQuestion`. Route on `tool_name == "AskUserQuestion"`.

### Input format Claude sends

```json
{
  "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
    }
  ]
}
```

### Response format you return

```json
{
  "questions": [ /* pass through original */ ],
  "answers": {
    "How should I format the output?": "Summary",
    "Which sections should I include?": "Introduction, Conclusion"
  }
}
```

- Single-select: one label
- Multi-select: labels joined with `", "`
- Free text: user's raw text (add an "Other" option in your UI)

### Option previews (TypeScript only)

Set `toolConfig.askUserQuestion.previewFormat` to `"markdown"` or `"html"` — Claude adds a `preview` field to options where a visual comparison helps. Check for `undefined` before rendering.

## Complete Python Example

```python
def parse_response(response: str, options: list) -> str:
    try:
        indices = [int(s.strip()) - 1 for s in response.split(",")]
        labels = [options[i]["label"] for i in indices if 0 <= i < len(options)]
        return ", ".join(labels) if labels else response
    except ValueError:
        return response

async def handle_ask_user_question(input_data: dict) -> PermissionResultAllow:
    answers = {}
    for q in input_data.get("questions", []):
        print(f"\n{q['header']}: {q['question']}")
        for i, opt in enumerate(q["options"]):
            print(f"  {i+1}. {opt['label']} - {opt['description']}")
        response = input("Your choice: ").strip()
        answers[q["question"]] = parse_response(response, q["options"])
    return PermissionResultAllow(
        updated_input={"questions": input_data.get("questions", []), "answers": answers}
    )

async def can_use_tool(tool_name, input_data, context):
    if tool_name == "AskUserQuestion":
        return await handle_ask_user_question(input_data)
    # display tool info, prompt y/n
    response = input(f"Allow {tool_name}? (y/n): ")
    if response.lower() == "y":
        return PermissionResultAllow(updated_input=input_data)
    return PermissionResultDeny(message="User denied this action")
```

## Limitations

- `AskUserQuestion` is **not available in subagents** spawned via the Agent tool
- Each call supports **1–4 questions**, each with **2–4 options**

## Alternatives to canUseTool

| Mechanism | Best for |
|-----------|----------|
| [[wiki/agent-sdk/hooks-guide\|Hooks]] | Auto-allow/deny without prompting; external notifications (Slack, email) via `PermissionRequest` hook |
| Streaming input | Interrupting mid-task, providing context, chat interfaces |
| [[wiki/agent-sdk/custom-tools\|Custom tools]] | Structured forms, multi-step workflows, external approval systems |

## Key Takeaways

- `canUseTool` is the single callback for both tool approvals and `AskUserQuestion` — branch on `tool_name`
- Python requires streaming mode + a `PreToolUse` dummy hook to keep the stream alive
- Deny responses include a message Claude reads — use it to redirect, not just block
- `AskUserQuestion` is especially common in plan mode; include it in your `tools` array if you define one
- You can modify tool input before allowing (sanitize, constrain) — not just pass-through
- For automation without user prompts, use [[wiki/agent-sdk/hooks-guide\|hooks]] instead of `canUseTool`

## Sources

- `raw/Handle approvals and user input.md` — official Agent SDK docs on user input handling
