3169 lines
No EOL
102 KiB
Markdown
3169 lines
No EOL
102 KiB
Markdown
---
|
||
title: "Agent SDK reference - Python"
|
||
source: "https://code.claude.com/docs/en/agent-sdk/python"
|
||
author:
|
||
published:
|
||
created: 2026-04-17
|
||
description: "Complete API reference for the Python Agent SDK, including all functions, types, and classes."
|
||
tags:
|
||
- "clippings"
|
||
---
|
||
## Installation
|
||
|
||
```shellscript
|
||
pip install claude-agent-sdk
|
||
```
|
||
|
||
## Choosing between query() and ClaudeSDKClient
|
||
|
||
The Python SDK provides two ways to interact with Claude Code:
|
||
|
||
### Quick comparison
|
||
|
||
| Feature | `query()` | `ClaudeSDKClient` |
|
||
| --- | --- | --- |
|
||
| **Session** | Creates new session each time | Reuses same session |
|
||
| **Conversation** | Single exchange | Multiple exchanges in same context |
|
||
| **Connection** | Managed automatically | Manual control |
|
||
| **Streaming Input** | ✅ Supported | ✅ Supported |
|
||
| **Interrupts** | ❌ Not supported | ✅ Supported |
|
||
| **Hooks** | ✅ Supported | ✅ Supported |
|
||
| **Custom Tools** | ✅ Supported | ✅ Supported |
|
||
| **Continue Chat** | ❌ New session each time | ✅ Maintains conversation |
|
||
| **Use Case** | One-off tasks | Continuous conversations |
|
||
|
||
### When to use query() (new session each time)
|
||
|
||
**Best for:**
|
||
|
||
- One-off questions where you don’t need conversation history
|
||
- Independent tasks that don’t require context from previous exchanges
|
||
- Simple automation scripts
|
||
- When you want a fresh start each time
|
||
|
||
### When to use ClaudeSDKClient (continuous conversation)
|
||
|
||
**Best for:**
|
||
|
||
- **Continuing conversations** - When you need Claude to remember context
|
||
- **Follow-up questions** - Building on previous responses
|
||
- **Interactive applications** - Chat interfaces, REPLs
|
||
- **Response-driven logic** - When next action depends on Claude’s response
|
||
- **Session control** - Managing conversation lifecycle explicitly
|
||
|
||
## Functions
|
||
|
||
### query()
|
||
|
||
Creates a new session for each interaction with Claude Code. Returns an async iterator that yields messages as they arrive. Each call to `query()` starts fresh with no memory of previous interactions.
|
||
|
||
```python
|
||
async def query(
|
||
*,
|
||
prompt: str | AsyncIterable[dict[str, Any]],
|
||
options: ClaudeAgentOptions | None = None,
|
||
transport: Transport | None = None
|
||
) -> AsyncIterator[Message]
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
| Parameter | Type | Description |
|
||
| --- | --- | --- |
|
||
| `prompt` | `str \| AsyncIterable[dict]` | The input prompt as a string or async iterable for streaming mode |
|
||
| `options` | `ClaudeAgentOptions \| None` | Optional configuration object (defaults to `ClaudeAgentOptions()` if None) |
|
||
| `transport` | `Transport \| None` | Optional custom transport for communicating with the CLI process |
|
||
|
||
#### Returns
|
||
|
||
Returns an `AsyncIterator[Message]` that yields messages from the conversation.
|
||
|
||
#### Example - With options
|
||
|
||
```python
|
||
import asyncio
|
||
from claude_agent_sdk import query, ClaudeAgentOptions
|
||
|
||
async def main():
|
||
options = ClaudeAgentOptions(
|
||
system_prompt="You are an expert Python developer",
|
||
permission_mode="acceptEdits",
|
||
cwd="/home/user/project",
|
||
)
|
||
|
||
async for message in query(prompt="Create a Python web server", options=options):
|
||
print(message)
|
||
|
||
asyncio.run(main())
|
||
```
|
||
|
||
### tool()
|
||
|
||
Decorator for defining MCP tools with type safety.
|
||
|
||
```python
|
||
def tool(
|
||
name: str,
|
||
description: str,
|
||
input_schema: type | dict[str, Any],
|
||
annotations: ToolAnnotations | None = None
|
||
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
| Parameter | Type | Description |
|
||
| --- | --- | --- |
|
||
| `name` | `str` | Unique identifier for the tool |
|
||
| `description` | `str` | Human-readable description of what the tool does |
|
||
| `input_schema` | `type \| dict[str, Any]` | Schema defining the tool’s input parameters (see below) |
|
||
| `annotations` | [`ToolAnnotations`](#tool-annotations) ` \| None` | Optional MCP tool annotations providing behavioral hints to clients |
|
||
|
||
#### Input schema options
|
||
|
||
1. **Simple type mapping** (recommended):
|
||
```python
|
||
{"text": str, "count": int, "enabled": bool}
|
||
```
|
||
2. **JSON Schema format** (for complex validation):
|
||
```python
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"text": {"type": "string"},
|
||
"count": {"type": "integer", "minimum": 0},
|
||
},
|
||
"required": ["text"],
|
||
}
|
||
```
|
||
|
||
#### Returns
|
||
|
||
A decorator function that wraps the tool implementation and returns an `SdkMcpTool` instance.
|
||
|
||
#### Example
|
||
|
||
```python
|
||
from claude_agent_sdk import tool
|
||
from typing import Any
|
||
|
||
@tool("greet", "Greet a user", {"name": str})
|
||
async def greet(args: dict[str, Any]) -> dict[str, Any]:
|
||
return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]}
|
||
```
|
||
|
||
#### ToolAnnotations
|
||
|
||
Re-exported from `mcp.types` (also available as `from claude_agent_sdk import ToolAnnotations`). All fields are optional hints; clients should not rely on them for security decisions.
|
||
|
||
| Field | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `title` | `str \| None` | `None` | Human-readable title for the tool |
|
||
| `readOnlyHint` | `bool \| None` | `False` | If `True`, the tool does not modify its environment |
|
||
| `destructiveHint` | `bool \| None` | `True` | If `True`, the tool may perform destructive updates (only meaningful when `readOnlyHint` is `False`) |
|
||
| `idempotentHint` | `bool \| None` | `False` | If `True`, repeated calls with the same arguments have no additional effect (only meaningful when `readOnlyHint` is `False`) |
|
||
| `openWorldHint` | `bool \| None` | `True` | If `True`, the tool interacts with external entities (for example, web search). If `False`, the tool’s domain is closed (for example, a memory tool) |
|
||
|
||
```python
|
||
from claude_agent_sdk import tool, ToolAnnotations
|
||
from typing import Any
|
||
|
||
@tool(
|
||
"search",
|
||
"Search the web",
|
||
{"query": str},
|
||
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
|
||
)
|
||
async def search(args: dict[str, Any]) -> dict[str, Any]:
|
||
return {"content": [{"type": "text", "text": f"Results for: {args['query']}"}]}
|
||
```
|
||
|
||
### create\_sdk\_mcp\_server()
|
||
|
||
Create an in-process MCP server that runs within your Python application.
|
||
|
||
```python
|
||
def create_sdk_mcp_server(
|
||
name: str,
|
||
version: str = "1.0.0",
|
||
tools: list[SdkMcpTool[Any]] | None = None
|
||
) -> McpSdkServerConfig
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
| Parameter | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `name` | `str` | \- | Unique identifier for the server |
|
||
| `version` | `str` | `"1.0.0"` | Server version string |
|
||
| `tools` | `list[SdkMcpTool[Any]] \| None` | `None` | List of tool functions created with `@tool` decorator |
|
||
|
||
#### Returns
|
||
|
||
Returns an `McpSdkServerConfig` object that can be passed to `ClaudeAgentOptions.mcp_servers`.
|
||
|
||
#### Example
|
||
|
||
```python
|
||
from claude_agent_sdk import tool, create_sdk_mcp_server
|
||
|
||
@tool("add", "Add two numbers", {"a": float, "b": float})
|
||
async def add(args):
|
||
return {"content": [{"type": "text", "text": f"Sum: {args['a'] + args['b']}"}]}
|
||
|
||
@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
|
||
async def multiply(args):
|
||
return {"content": [{"type": "text", "text": f"Product: {args['a'] * args['b']}"}]}
|
||
|
||
calculator = create_sdk_mcp_server(
|
||
name="calculator",
|
||
version="2.0.0",
|
||
tools=[add, multiply], # Pass decorated functions
|
||
)
|
||
|
||
# Use with Claude
|
||
options = ClaudeAgentOptions(
|
||
mcp_servers={"calc": calculator},
|
||
allowed_tools=["mcp__calc__add", "mcp__calc__multiply"],
|
||
)
|
||
```
|
||
|
||
### list\_sessions()
|
||
|
||
Lists past sessions with metadata. Filter by project directory or list sessions across all projects. Synchronous; returns immediately.
|
||
|
||
```python
|
||
def list_sessions(
|
||
directory: str | None = None,
|
||
limit: int | None = None,
|
||
include_worktrees: bool = True
|
||
) -> list[SDKSessionInfo]
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
| Parameter | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `directory` | `str \| None` | `None` | Directory to list sessions for. When omitted, returns sessions across all projects |
|
||
| `limit` | `int \| None` | `None` | Maximum number of sessions to return |
|
||
| `include_worktrees` | `bool` | `True` | When `directory` is inside a git repository, include sessions from all worktree paths |
|
||
|
||
#### Return type: SDKSessionInfo
|
||
|
||
| Property | Type | Description |
|
||
| --- | --- | --- |
|
||
| `session_id` | `str` | Unique session identifier |
|
||
| `summary` | `str` | Display title: custom title, auto-generated summary, or first prompt |
|
||
| `last_modified` | `int` | Last modified time in milliseconds since epoch |
|
||
| `file_size` | `int \| None` | Session file size in bytes (`None` for remote storage backends) |
|
||
| `custom_title` | `str \| None` | User-set session title |
|
||
| `first_prompt` | `str \| None` | First meaningful user prompt in the session |
|
||
| `git_branch` | `str \| None` | Git branch at the end of the session |
|
||
| `cwd` | `str \| None` | Working directory for the session |
|
||
| `tag` | `str \| None` | User-set session tag (see [`tag_session()`](#tag-session)) |
|
||
| `created_at` | `int \| None` | Session creation time in milliseconds since epoch |
|
||
|
||
#### Example
|
||
|
||
Print the 10 most recent sessions for a project. Results are sorted by `last_modified` descending, so the first item is the newest. Omit `directory` to search across all projects.
|
||
|
||
```python
|
||
from claude_agent_sdk import list_sessions
|
||
|
||
for session in list_sessions(directory="/path/to/project", limit=10):
|
||
print(f"{session.summary} ({session.session_id})")
|
||
```
|
||
|
||
### get\_session\_messages()
|
||
|
||
Retrieves messages from a past session. Synchronous; returns immediately.
|
||
|
||
```python
|
||
def get_session_messages(
|
||
session_id: str,
|
||
directory: str | None = None,
|
||
limit: int | None = None,
|
||
offset: int = 0
|
||
) -> list[SessionMessage]
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
| Parameter | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `session_id` | `str` | required | The session ID to retrieve messages for |
|
||
| `directory` | `str \| None` | `None` | Project directory to look in. When omitted, searches all projects |
|
||
| `limit` | `int \| None` | `None` | Maximum number of messages to return |
|
||
| `offset` | `int` | `0` | Number of messages to skip from the start |
|
||
|
||
#### Return type: SessionMessage
|
||
|
||
| Property | Type | Description |
|
||
| --- | --- | --- |
|
||
| `type` | `Literal["user", "assistant"]` | Message role |
|
||
| `uuid` | `str` | Unique message identifier |
|
||
| `session_id` | `str` | Session identifier |
|
||
| `message` | `Any` | Raw message content |
|
||
| `parent_tool_use_id` | `None` | Reserved for future use |
|
||
|
||
#### Example
|
||
|
||
```python
|
||
from claude_agent_sdk import list_sessions, get_session_messages
|
||
|
||
sessions = list_sessions(limit=1)
|
||
if sessions:
|
||
messages = get_session_messages(sessions[0].session_id)
|
||
for msg in messages:
|
||
print(f"[{msg.type}] {msg.uuid}")
|
||
```
|
||
|
||
### get\_session\_info()
|
||
|
||
Reads metadata for a single session by ID without scanning the full project directory. Synchronous; returns immediately.
|
||
|
||
```python
|
||
def get_session_info(
|
||
session_id: str,
|
||
directory: str | None = None,
|
||
) -> SDKSessionInfo | None
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
| Parameter | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `session_id` | `str` | required | UUID of the session to look up |
|
||
| `directory` | `str \| None` | `None` | Project directory path. When omitted, searches all project directories |
|
||
|
||
Returns [`SDKSessionInfo`](#return-type-sdk-session-info), or `None` if the session is not found.
|
||
|
||
#### Example
|
||
|
||
Look up a single session’s metadata without scanning the project directory. Useful when you already have a session ID from a previous run.
|
||
|
||
```python
|
||
from claude_agent_sdk import get_session_info
|
||
|
||
info = get_session_info("550e8400-e29b-41d4-a716-446655440000")
|
||
if info:
|
||
print(f"{info.summary} (branch: {info.git_branch}, tag: {info.tag})")
|
||
```
|
||
|
||
### rename\_session()
|
||
|
||
Renames a session by appending a custom-title entry. Repeated calls are safe; the most recent title wins. Synchronous.
|
||
|
||
```python
|
||
def rename_session(
|
||
session_id: str,
|
||
title: str,
|
||
directory: str | None = None,
|
||
) -> None
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
| Parameter | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `session_id` | `str` | required | UUID of the session to rename |
|
||
| `title` | `str` | required | New title. Must be non-empty after stripping whitespace |
|
||
| `directory` | `str \| None` | `None` | Project directory path. When omitted, searches all project directories |
|
||
|
||
Raises `ValueError` if `session_id` is not a valid UUID or `title` is empty; `FileNotFoundError` if the session cannot be found.
|
||
|
||
#### Example
|
||
|
||
Rename the most recent session so it’s easier to find later. The new title appears in [`SDKSessionInfo.custom_title`](#return-type-sdk-session-info) on subsequent reads.
|
||
|
||
```python
|
||
from claude_agent_sdk import list_sessions, rename_session
|
||
|
||
sessions = list_sessions(directory="/path/to/project", limit=1)
|
||
if sessions:
|
||
rename_session(sessions[0].session_id, "Refactor auth module")
|
||
```
|
||
|
||
### tag\_session()
|
||
|
||
Tags a session. Pass `None` to clear the tag. Repeated calls are safe; the most recent tag wins. Synchronous.
|
||
|
||
```python
|
||
def tag_session(
|
||
session_id: str,
|
||
tag: str | None,
|
||
directory: str | None = None,
|
||
) -> None
|
||
```
|
||
|
||
#### Parameters
|
||
|
||
| Parameter | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `session_id` | `str` | required | UUID of the session to tag |
|
||
| `tag` | `str \| None` | required | Tag string, or `None` to clear. Unicode-sanitized before storing |
|
||
| `directory` | `str \| None` | `None` | Project directory path. When omitted, searches all project directories |
|
||
|
||
Raises `ValueError` if `session_id` is not a valid UUID or `tag` is empty after sanitization; `FileNotFoundError` if the session cannot be found.
|
||
|
||
#### Example
|
||
|
||
Tag a session, then filter by that tag on a later read. Pass `None` to clear an existing tag.
|
||
|
||
```python
|
||
from claude_agent_sdk import list_sessions, tag_session
|
||
|
||
# Tag a session
|
||
tag_session("550e8400-e29b-41d4-a716-446655440000", "needs-review")
|
||
|
||
# Later: find all sessions with that tag
|
||
for session in list_sessions(directory="/path/to/project"):
|
||
if session.tag == "needs-review":
|
||
print(session.summary)
|
||
```
|
||
|
||
## Classes
|
||
|
||
### ClaudeSDKClient
|
||
|
||
**Maintains a conversation session across multiple exchanges.** This is the Python equivalent of how the TypeScript SDK’s `query()` function works internally - it creates a client object that can continue conversations.
|
||
|
||
#### Key Features
|
||
|
||
- **Session continuity**: Maintains conversation context across multiple `query()` calls
|
||
- **Same conversation**: The session retains previous messages
|
||
- **Interrupt support**: Can stop execution mid-task
|
||
- **Explicit lifecycle**: You control when the session starts and ends
|
||
- **Response-driven flow**: Can react to responses and send follow-ups
|
||
- **Custom tools and hooks**: Supports custom tools (created with `@tool` decorator) and hooks
|
||
|
||
```python
|
||
class ClaudeSDKClient:
|
||
def __init__(self, options: ClaudeAgentOptions | None = None, transport: Transport | None = None)
|
||
async def connect(self, prompt: str | AsyncIterable[dict] | None = None) -> None
|
||
async def query(self, prompt: str | AsyncIterable[dict], session_id: str = "default") -> None
|
||
async def receive_messages(self) -> AsyncIterator[Message]
|
||
async def receive_response(self) -> AsyncIterator[Message]
|
||
async def interrupt(self) -> None
|
||
async def set_permission_mode(self, mode: str) -> None
|
||
async def set_model(self, model: str | None = None) -> None
|
||
async def rewind_files(self, user_message_id: str) -> None
|
||
async def get_mcp_status(self) -> McpStatusResponse
|
||
async def reconnect_mcp_server(self, server_name: str) -> None
|
||
async def toggle_mcp_server(self, server_name: str, enabled: bool) -> None
|
||
async def stop_task(self, task_id: str) -> None
|
||
async def get_server_info(self) -> dict[str, Any] | None
|
||
async def disconnect(self) -> None
|
||
```
|
||
|
||
#### Methods
|
||
|
||
| Method | Description |
|
||
| --- | --- |
|
||
| `__init__(options)` | Initialize the client with optional configuration |
|
||
| `connect(prompt)` | Connect to Claude with an optional initial prompt or message stream |
|
||
| `query(prompt, session_id)` | Send a new request in streaming mode |
|
||
| `receive_messages()` | Receive all messages from Claude as an async iterator |
|
||
| `receive_response()` | Receive messages until and including a ResultMessage |
|
||
| `interrupt()` | Send interrupt signal (only works in streaming mode) |
|
||
| `set_permission_mode(mode)` | Change the permission mode for the current session |
|
||
| `set_model(model)` | Change the model for the current session. Pass `None` to reset to default |
|
||
| `rewind_files(user_message_id)` | Restore files to their state at the specified user message. Requires `enable_file_checkpointing=True`. See [File checkpointing](https://code.claude.com/docs/en/agent-sdk/file-checkpointing) |
|
||
| `get_mcp_status()` | Get the status of all configured MCP servers. Returns [`McpStatusResponse`](#mcp-status-response) |
|
||
| `reconnect_mcp_server(server_name)` | Retry connecting to an MCP server that failed or was disconnected |
|
||
| `toggle_mcp_server(server_name, enabled)` | Enable or disable an MCP server mid-session. Disabling removes its tools |
|
||
| `stop_task(task_id)` | Stop a running background task. A [`TaskNotificationMessage`](#task-notification-message) with status `"stopped"` follows in the message stream |
|
||
| `get_server_info()` | Get server information including session ID and capabilities |
|
||
| `disconnect()` | Disconnect from Claude |
|
||
|
||
#### Context Manager Support
|
||
|
||
The client can be used as an async context manager for automatic connection management:
|
||
|
||
```python
|
||
async with ClaudeSDKClient() as client:
|
||
await client.query("Hello Claude")
|
||
async for message in client.receive_response():
|
||
print(message)
|
||
```
|
||
|
||
> **Important:** When iterating over messages, avoid using `break` to exit early as this can cause asyncio cleanup issues. Instead, let the iteration complete naturally or use flags to track when you’ve found what you need.
|
||
|
||
#### Example - Continuing a conversation
|
||
|
||
```python
|
||
import asyncio
|
||
from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock, ResultMessage
|
||
|
||
async def main():
|
||
async with ClaudeSDKClient() as client:
|
||
# First question
|
||
await client.query("What's the capital of France?")
|
||
|
||
# Process response
|
||
async for message in client.receive_response():
|
||
if isinstance(message, AssistantMessage):
|
||
for block in message.content:
|
||
if isinstance(block, TextBlock):
|
||
print(f"Claude: {block.text}")
|
||
|
||
# Follow-up question - the session retains the previous context
|
||
await client.query("What's the population of that city?")
|
||
|
||
async for message in client.receive_response():
|
||
if isinstance(message, AssistantMessage):
|
||
for block in message.content:
|
||
if isinstance(block, TextBlock):
|
||
print(f"Claude: {block.text}")
|
||
|
||
# Another follow-up - still in the same conversation
|
||
await client.query("What are some famous landmarks there?")
|
||
|
||
async for message in client.receive_response():
|
||
if isinstance(message, AssistantMessage):
|
||
for block in message.content:
|
||
if isinstance(block, TextBlock):
|
||
print(f"Claude: {block.text}")
|
||
|
||
asyncio.run(main())
|
||
```
|
||
|
||
#### Example - Streaming input with ClaudeSDKClient
|
||
|
||
```python
|
||
import asyncio
|
||
from claude_agent_sdk import ClaudeSDKClient
|
||
|
||
async def message_stream():
|
||
"""Generate messages dynamically."""
|
||
yield {
|
||
"type": "user",
|
||
"message": {"role": "user", "content": "Analyze the following data:"},
|
||
}
|
||
await asyncio.sleep(0.5)
|
||
yield {
|
||
"type": "user",
|
||
"message": {"role": "user", "content": "Temperature: 25°C, Humidity: 60%"},
|
||
}
|
||
await asyncio.sleep(0.5)
|
||
yield {
|
||
"type": "user",
|
||
"message": {"role": "user", "content": "What patterns do you see?"},
|
||
}
|
||
|
||
async def main():
|
||
async with ClaudeSDKClient() as client:
|
||
# Stream input to Claude
|
||
await client.query(message_stream())
|
||
|
||
# Process response
|
||
async for message in client.receive_response():
|
||
print(message)
|
||
|
||
# Follow-up in same session
|
||
await client.query("Should we be concerned about these readings?")
|
||
|
||
async for message in client.receive_response():
|
||
print(message)
|
||
|
||
asyncio.run(main())
|
||
```
|
||
|
||
#### Example - Using interrupts
|
||
|
||
```python
|
||
import asyncio
|
||
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, ResultMessage
|
||
|
||
async def interruptible_task():
|
||
options = ClaudeAgentOptions(allowed_tools=["Bash"], permission_mode="acceptEdits")
|
||
|
||
async with ClaudeSDKClient(options=options) as client:
|
||
# Start a long-running task
|
||
await client.query("Count from 1 to 100 slowly, using the bash sleep command")
|
||
|
||
# Let it run for a bit
|
||
await asyncio.sleep(2)
|
||
|
||
# Interrupt the task
|
||
await client.interrupt()
|
||
print("Task interrupted!")
|
||
|
||
# Drain the interrupted task's messages (including its ResultMessage)
|
||
async for message in client.receive_response():
|
||
if isinstance(message, ResultMessage):
|
||
print(f"Interrupted task finished with subtype={message.subtype!r}")
|
||
# subtype is "error_during_execution" for interrupted tasks
|
||
|
||
# Send a new command
|
||
await client.query("Just say hello instead")
|
||
|
||
# Now receive the new response
|
||
async for message in client.receive_response():
|
||
if isinstance(message, ResultMessage) and message.subtype == "success":
|
||
print(f"New result: {message.result}")
|
||
|
||
asyncio.run(interruptible_task())
|
||
```
|
||
|
||
**Buffer behavior after interrupt:** `interrupt()` sends a stop signal but does not clear the message buffer. Messages already produced by the interrupted task, including its `ResultMessage` (with `subtype="error_during_execution"`), remain in the stream. You must drain them with `receive_response()` before reading the response to a new query. If you send a new query immediately after `interrupt()` and call `receive_response()` only once, you’ll receive the interrupted task’s messages, not the new query’s response.
|
||
|
||
#### Example - Advanced permission control
|
||
|
||
```python
|
||
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
|
||
from claude_agent_sdk.types import (
|
||
PermissionResultAllow,
|
||
PermissionResultDeny,
|
||
ToolPermissionContext,
|
||
)
|
||
|
||
async def custom_permission_handler(
|
||
tool_name: str, input_data: dict, context: ToolPermissionContext
|
||
) -> PermissionResultAllow | PermissionResultDeny:
|
||
"""Custom logic for tool permissions."""
|
||
|
||
# Block writes to system directories
|
||
if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
|
||
return PermissionResultDeny(
|
||
message="System directory write not allowed", interrupt=True
|
||
)
|
||
|
||
# Redirect sensitive file operations
|
||
if tool_name in ["Write", "Edit"] and "config" in input_data.get("file_path", ""):
|
||
safe_path = f"./sandbox/{input_data['file_path']}"
|
||
return PermissionResultAllow(
|
||
updated_input={**input_data, "file_path": safe_path}
|
||
)
|
||
|
||
# Allow everything else
|
||
return PermissionResultAllow(updated_input=input_data)
|
||
|
||
async def main():
|
||
options = ClaudeAgentOptions(
|
||
can_use_tool=custom_permission_handler, allowed_tools=["Read", "Write", "Edit"]
|
||
)
|
||
|
||
async with ClaudeSDKClient(options=options) as client:
|
||
await client.query("Update the system config file")
|
||
|
||
async for message in client.receive_response():
|
||
# Will use sandbox path instead
|
||
print(message)
|
||
|
||
asyncio.run(main())
|
||
```
|
||
|
||
## Types
|
||
|
||
**`@dataclass` vs `TypedDict`:** This SDK uses two kinds of types. Classes decorated with `@dataclass` (such as `ResultMessage`, `AgentDefinition`, `TextBlock`) are object instances at runtime and support attribute access: `msg.result`. Classes defined with `TypedDict` (such as `ThinkingConfigEnabled`, `McpStdioServerConfig`, `SyncHookJSONOutput`) are **plain dicts at runtime** and require key access: `config["budget_tokens"]`, not `config.budget_tokens`. The `ClassName(field=value)` call syntax works for both, but only dataclasses produce objects with attributes.
|
||
|
||
### SdkMcpTool
|
||
|
||
Definition for an SDK MCP tool created with the `@tool` decorator.
|
||
|
||
```python
|
||
@dataclass
|
||
class SdkMcpTool(Generic[T]):
|
||
name: str
|
||
description: str
|
||
input_schema: type[T] | dict[str, Any]
|
||
handler: Callable[[T], Awaitable[dict[str, Any]]]
|
||
annotations: ToolAnnotations | None = None
|
||
```
|
||
|
||
| Property | Type | Description |
|
||
| --- | --- | --- |
|
||
| `name` | `str` | Unique identifier for the tool |
|
||
| `description` | `str` | Human-readable description |
|
||
| `input_schema` | `type[T] \| dict[str, Any]` | Schema for input validation |
|
||
| `handler` | `Callable[[T], Awaitable[dict[str, Any]]]` | Async function that handles tool execution |
|
||
| `annotations` | `ToolAnnotations \| None` | Optional MCP tool annotations (e.g., `readOnlyHint`, `destructiveHint`, `openWorldHint`). From `mcp.types` |
|
||
|
||
### Transport
|
||
|
||
Abstract base class for custom transport implementations. Use this to communicate with the Claude process over a custom channel (for example, a remote connection instead of a local subprocess).
|
||
|
||
This is a low-level internal API. The interface may change in future releases. Custom implementations must be updated to match any interface changes.
|
||
|
||
```python
|
||
from abc import ABC, abstractmethod
|
||
from collections.abc import AsyncIterator
|
||
from typing import Any
|
||
|
||
class Transport(ABC):
|
||
@abstractmethod
|
||
async def connect(self) -> None: ...
|
||
|
||
@abstractmethod
|
||
async def write(self, data: str) -> None: ...
|
||
|
||
@abstractmethod
|
||
def read_messages(self) -> AsyncIterator[dict[str, Any]]: ...
|
||
|
||
@abstractmethod
|
||
async def close(self) -> None: ...
|
||
|
||
@abstractmethod
|
||
def is_ready(self) -> bool: ...
|
||
|
||
@abstractmethod
|
||
async def end_input(self) -> None: ...
|
||
```
|
||
|
||
| Method | Description |
|
||
| --- | --- |
|
||
| `connect()` | Connect the transport and prepare for communication |
|
||
| `write(data)` | Write raw data (JSON + newline) to the transport |
|
||
| `read_messages()` | Async iterator that yields parsed JSON messages |
|
||
| `close()` | Close the connection and clean up resources |
|
||
| `is_ready()` | Returns `True` if the transport can send and receive |
|
||
| `end_input()` | Close the input stream (for example, close stdin for subprocess transports) |
|
||
|
||
Import: `from claude_agent_sdk import Transport`
|
||
|
||
### ClaudeAgentOptions
|
||
|
||
Configuration dataclass for Claude Code queries.
|
||
|
||
```python
|
||
@dataclass
|
||
class ClaudeAgentOptions:
|
||
tools: list[str] | ToolsPreset | None = None
|
||
allowed_tools: list[str] = field(default_factory=list)
|
||
system_prompt: str | SystemPromptPreset | None = None
|
||
mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)
|
||
permission_mode: PermissionMode | None = None
|
||
continue_conversation: bool = False
|
||
resume: str | None = None
|
||
max_turns: int | None = None
|
||
max_budget_usd: float | None = None
|
||
disallowed_tools: list[str] = field(default_factory=list)
|
||
model: str | None = None
|
||
fallback_model: str | None = None
|
||
betas: list[SdkBeta] = field(default_factory=list)
|
||
output_format: dict[str, Any] | None = None
|
||
permission_prompt_tool_name: str | None = None
|
||
cwd: str | Path | None = None
|
||
cli_path: str | Path | None = None
|
||
settings: str | None = None
|
||
add_dirs: list[str | Path] = field(default_factory=list)
|
||
env: dict[str, str] = field(default_factory=dict)
|
||
extra_args: dict[str, str | None] = field(default_factory=dict)
|
||
max_buffer_size: int | None = None
|
||
debug_stderr: Any = sys.stderr # Deprecated
|
||
stderr: Callable[[str], None] | None = None
|
||
can_use_tool: CanUseTool | None = None
|
||
hooks: dict[HookEvent, list[HookMatcher]] | None = None
|
||
user: str | None = None
|
||
include_partial_messages: bool = False
|
||
fork_session: bool = False
|
||
agents: dict[str, AgentDefinition] | None = None
|
||
setting_sources: list[SettingSource] | None = None
|
||
sandbox: SandboxSettings | None = None
|
||
plugins: list[SdkPluginConfig] = field(default_factory=list)
|
||
max_thinking_tokens: int | None = None # Deprecated: use thinking instead
|
||
thinking: ThinkingConfig | None = None
|
||
effort: Literal["low", "medium", "high", "xhigh", "max"] | None = None
|
||
enable_file_checkpointing: bool = False
|
||
```
|
||
|
||
| Property | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `tools` | `list[str] \| ToolsPreset \| None` | `None` | Tools configuration. Use `{"type": "preset", "preset": "claude_code"}` for Claude Code’s default tools |
|
||
| `allowed_tools` | `list[str]` | `[]` | Tools to auto-approve without prompting. This does not restrict Claude to only these tools; unlisted tools fall through to `permission_mode` and `can_use_tool`. Use `disallowed_tools` to block tools. See [Permissions](https://code.claude.com/docs/en/agent-sdk/permissions#allow-and-deny-rules) |
|
||
| `system_prompt` | `str \| SystemPromptPreset \| None` | `None` | System prompt configuration. Pass a string for custom prompt, or use `{"type": "preset", "preset": "claude_code"}` for Claude Code’s system prompt. Add `"append"` to extend the preset |
|
||
| `mcp_servers` | `dict[str, McpServerConfig] \| str \| Path` | `{}` | MCP server configurations or path to config file |
|
||
| `permission_mode` | `PermissionMode \| None` | `None` | Permission mode for tool usage |
|
||
| `continue_conversation` | `bool` | `False` | Continue the most recent conversation |
|
||
| `resume` | `str \| None` | `None` | Session ID to resume |
|
||
| `max_turns` | `int \| None` | `None` | Maximum agentic turns (tool-use round trips) |
|
||
| `max_budget_usd` | `float \| None` | `None` | Stop the query when the client-side cost estimate reaches this USD value. Compared against the same estimate as `total_cost_usd`; see [Track cost and usage](https://code.claude.com/docs/en/agent-sdk/cost-tracking) for accuracy caveats |
|
||
| `disallowed_tools` | `list[str]` | `[]` | Tools to always deny. Deny rules are checked first and override `allowed_tools` and `permission_mode` (including `bypassPermissions`) |
|
||
| `enable_file_checkpointing` | `bool` | `False` | Enable file change tracking for rewinding. See [File checkpointing](https://code.claude.com/docs/en/agent-sdk/file-checkpointing) |
|
||
| `model` | `str \| None` | `None` | Claude model to use |
|
||
| `fallback_model` | `str \| None` | `None` | Fallback model to use if the primary model fails |
|
||
| `betas` | `list[SdkBeta]` | `[]` | Beta features to enable. See [`SdkBeta`](#sdk-beta) for available options |
|
||
| `output_format` | `dict[str, Any] \| None` | `None` | Output format for structured responses (e.g., `{"type": "json_schema", "schema": {...}}`). See [Structured outputs](https://code.claude.com/docs/en/agent-sdk/structured-outputs) for details |
|
||
| `permission_prompt_tool_name` | `str \| None` | `None` | MCP tool name for permission prompts |
|
||
| `cwd` | `str \| Path \| None` | `None` | Current working directory |
|
||
| `cli_path` | `str \| Path \| None` | `None` | Custom path to the Claude Code CLI executable |
|
||
| `settings` | `str \| None` | `None` | Path to settings file |
|
||
| `add_dirs` | `list[str \| Path]` | `[]` | Additional directories Claude can access |
|
||
| `env` | `dict[str, str]` | `{}` | Environment variables |
|
||
| `extra_args` | `dict[str, str \| None]` | `{}` | Additional CLI arguments to pass directly to the CLI |
|
||
| `max_buffer_size` | `int \| None` | `None` | Maximum bytes when buffering CLI stdout |
|
||
| `debug_stderr` | `Any` | `sys.stderr` | *Deprecated* - File-like object for debug output. Use `stderr` callback instead |
|
||
| `stderr` | `Callable[[str], None] \| None` | `None` | Callback function for stderr output from CLI |
|
||
| `can_use_tool` | [`CanUseTool`](#can-use-tool) ` \| None` | `None` | Tool permission callback function. See [Permission types](#can-use-tool) for details |
|
||
| `hooks` | `dict[HookEvent, list[HookMatcher]] \| None` | `None` | Hook configurations for intercepting events |
|
||
| `user` | `str \| None` | `None` | User identifier |
|
||
| `include_partial_messages` | `bool` | `False` | Include partial message streaming events. When enabled, [`StreamEvent`](#stream-event) messages are yielded |
|
||
| `fork_session` | `bool` | `False` | When resuming with `resume`, fork to a new session ID instead of continuing the original session |
|
||
| `agents` | `dict[str, AgentDefinition] \| None` | `None` | Programmatically defined subagents |
|
||
| `plugins` | `list[SdkPluginConfig]` | `[]` | Load custom plugins from local paths. See [Plugins](https://code.claude.com/docs/en/agent-sdk/plugins) for details |
|
||
| `sandbox` | [`SandboxSettings`](#sandbox-settings) ` \| None` | `None` | Configure sandbox behavior programmatically. See [Sandbox settings](#sandbox-settings) for details |
|
||
| `setting_sources` | `list[SettingSource] \| None` | `None` (CLI defaults: all sources) | Control which filesystem settings to load. Pass `[]` to disable user, project, and local settings. Managed policy settings load regardless. See [Use Claude Code features](https://code.claude.com/docs/en/agent-sdk/claude-code-features#what-settingsources-does-not-control) |
|
||
| `max_thinking_tokens` | `int \| None` | `None` | *Deprecated* - Maximum tokens for thinking blocks. Use `thinking` instead |
|
||
| `thinking` | [`ThinkingConfig`](#thinking-config) ` \| None` | `None` | Controls extended thinking behavior. Takes precedence over `max_thinking_tokens` |
|
||
| `effort` | `Literal["low", "medium", "high", "xhigh", "max"] \| None` | `None` | Effort level for thinking depth |
|
||
|
||
### OutputFormat
|
||
|
||
Configuration for structured output validation. Pass this as a `dict` to the `output_format` field on `ClaudeAgentOptions`:
|
||
|
||
```python
|
||
# Expected dict shape for output_format
|
||
{
|
||
"type": "json_schema",
|
||
"schema": {...}, # Your JSON Schema definition
|
||
}
|
||
```
|
||
|
||
| Field | Required | Description |
|
||
| --- | --- | --- |
|
||
| `type` | Yes | Must be `"json_schema"` for JSON Schema validation |
|
||
| `schema` | Yes | JSON Schema definition for output validation |
|
||
|
||
### SystemPromptPreset
|
||
|
||
Configuration for using Claude Code’s preset system prompt with optional additions.
|
||
|
||
```python
|
||
class SystemPromptPreset(TypedDict):
|
||
type: Literal["preset"]
|
||
preset: Literal["claude_code"]
|
||
append: NotRequired[str]
|
||
exclude_dynamic_sections: NotRequired[bool]
|
||
```
|
||
|
||
| Field | Required | Description |
|
||
| --- | --- | --- |
|
||
| `type` | Yes | Must be `"preset"` to use a preset system prompt |
|
||
| `preset` | Yes | Must be `"claude_code"` to use Claude Code’s system prompt |
|
||
| `append` | No | Additional instructions to append to the preset system prompt |
|
||
| `exclude_dynamic_sections` | No | Move per-session context such as working directory, git status, and memory paths from the system prompt into the first user message. Improves prompt-cache reuse across users and machines. See [Modify system prompts](https://code.claude.com/docs/en/agent-sdk/modifying-system-prompts#improve-prompt-caching-across-users-and-machines) |
|
||
|
||
### SettingSource
|
||
|
||
Controls which filesystem-based configuration sources the SDK loads settings from.
|
||
|
||
```python
|
||
SettingSource = Literal["user", "project", "local"]
|
||
```
|
||
|
||
| Value | Description | Location |
|
||
| --- | --- | --- |
|
||
| `"user"` | Global user settings | `~/.claude/settings.json` |
|
||
| `"project"` | Shared project settings (version controlled) | `.claude/settings.json` |
|
||
| `"local"` | Local project settings (gitignored) | `.claude/settings.local.json` |
|
||
|
||
#### Default behavior
|
||
|
||
When `setting_sources` is omitted or `None`, `query()` loads the same filesystem settings as the Claude Code CLI: user, project, and local. Managed policy settings are loaded in all cases. See [What settingSources does not control](https://code.claude.com/docs/en/agent-sdk/claude-code-features#what-settingsources-does-not-control) for inputs that are read regardless of this option, and how to disable them.
|
||
|
||
#### Why use setting\_sources
|
||
|
||
**Disable filesystem settings:**
|
||
|
||
```python
|
||
# Do not load user, project, or local settings from disk
|
||
from claude_agent_sdk import query, ClaudeAgentOptions
|
||
|
||
async for message in query(
|
||
prompt="Analyze this code",
|
||
options=ClaudeAgentOptions(
|
||
setting_sources=[]
|
||
),
|
||
):
|
||
print(message)
|
||
```
|
||
|
||
In Python SDK 0.1.59 and earlier, an empty list was treated the same as omitting the option, so `setting_sources=[]` did not disable filesystem settings. Upgrade to a newer release if you need an empty list to take effect. The TypeScript SDK is not affected.
|
||
|
||
**Load all filesystem settings explicitly:**
|
||
|
||
```python
|
||
from claude_agent_sdk import query, ClaudeAgentOptions
|
||
|
||
async for message in query(
|
||
prompt="Analyze this code",
|
||
options=ClaudeAgentOptions(
|
||
setting_sources=["user", "project", "local"]
|
||
),
|
||
):
|
||
print(message)
|
||
```
|
||
|
||
**Load only specific setting sources:**
|
||
|
||
```python
|
||
# Load only project settings, ignore user and local
|
||
async for message in query(
|
||
prompt="Run CI checks",
|
||
options=ClaudeAgentOptions(
|
||
setting_sources=["project"] # Only .claude/settings.json
|
||
),
|
||
):
|
||
print(message)
|
||
```
|
||
|
||
**Testing and CI environments:**
|
||
|
||
```python
|
||
# Ensure consistent behavior in CI by excluding local settings
|
||
async for message in query(
|
||
prompt="Run tests",
|
||
options=ClaudeAgentOptions(
|
||
setting_sources=["project"], # Only team-shared settings
|
||
permission_mode="bypassPermissions",
|
||
),
|
||
):
|
||
print(message)
|
||
```
|
||
|
||
**SDK-only applications:**
|
||
|
||
```python
|
||
# Define everything programmatically.
|
||
# Pass [] to opt out of filesystem setting sources.
|
||
async for message in query(
|
||
prompt="Review this PR",
|
||
options=ClaudeAgentOptions(
|
||
setting_sources=[],
|
||
agents={...},
|
||
mcp_servers={...},
|
||
allowed_tools=["Read", "Grep", "Glob"],
|
||
),
|
||
):
|
||
print(message)
|
||
```
|
||
|
||
**Loading CLAUDE.md project instructions:**
|
||
|
||
```python
|
||
# Load project settings to include CLAUDE.md files
|
||
async for message in query(
|
||
prompt="Add a new feature following project conventions",
|
||
options=ClaudeAgentOptions(
|
||
system_prompt={
|
||
"type": "preset",
|
||
"preset": "claude_code", # Use Claude Code's system prompt
|
||
},
|
||
setting_sources=["project"], # Loads CLAUDE.md from project
|
||
allowed_tools=["Read", "Write", "Edit"],
|
||
),
|
||
):
|
||
print(message)
|
||
```
|
||
|
||
#### Settings precedence
|
||
|
||
When multiple sources are loaded, settings are merged with this precedence (highest to lowest):
|
||
|
||
1. Local settings (`.claude/settings.local.json`)
|
||
2. Project settings (`.claude/settings.json`)
|
||
3. User settings (`~/.claude/settings.json`)
|
||
|
||
Programmatic options such as `agents` and `allowed_tools` override user, project, and local filesystem settings. Managed policy settings take precedence over programmatic options.
|
||
|
||
### AgentDefinition
|
||
|
||
Configuration for a subagent defined programmatically.
|
||
|
||
```python
|
||
@dataclass
|
||
class AgentDefinition:
|
||
description: str
|
||
prompt: str
|
||
tools: list[str] | None = None
|
||
model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None
|
||
skills: list[str] | None = None
|
||
memory: Literal["user", "project", "local"] | None = None
|
||
mcpServers: list[str | dict[str, Any]] | None = None
|
||
```
|
||
|
||
| Field | Required | Description |
|
||
| --- | --- | --- |
|
||
| `description` | Yes | Natural language description of when to use this agent |
|
||
| `prompt` | Yes | The agent’s system prompt |
|
||
| `tools` | No | Array of allowed tool names. If omitted, inherits all tools |
|
||
| `model` | No | Model override for this agent. If omitted, uses the main model |
|
||
| `skills` | No | List of skill names available to this agent |
|
||
| `memory` | No | Memory source for this agent: `"user"`, `"project"`, or `"local"` |
|
||
| `mcpServers` | No | MCP servers available to this agent. Each entry is a server name or an inline `{name: config}` dict |
|
||
|
||
### PermissionMode
|
||
|
||
Permission modes for controlling tool execution.
|
||
|
||
```python
|
||
PermissionMode = Literal[
|
||
"default", # Standard permission behavior
|
||
"acceptEdits", # Auto-accept file edits
|
||
"plan", # Planning mode - no execution
|
||
"dontAsk", # Deny anything not pre-approved instead of prompting
|
||
"bypassPermissions", # Bypass all permission checks (use with caution)
|
||
]
|
||
```
|
||
|
||
### CanUseTool
|
||
|
||
Type alias for tool permission callback functions.
|
||
|
||
```python
|
||
CanUseTool = Callable[
|
||
[str, dict[str, Any], ToolPermissionContext], Awaitable[PermissionResult]
|
||
]
|
||
```
|
||
|
||
The callback receives:
|
||
|
||
- `tool_name`: Name of the tool being called
|
||
- `input_data`: The tool’s input parameters
|
||
- `context`: A `ToolPermissionContext` with additional information
|
||
|
||
Returns a `PermissionResult` (either `PermissionResultAllow` or `PermissionResultDeny`).
|
||
|
||
### ToolPermissionContext
|
||
|
||
Context information passed to tool permission callbacks.
|
||
|
||
```python
|
||
@dataclass
|
||
class ToolPermissionContext:
|
||
signal: Any | None = None # Future: abort signal support
|
||
suggestions: list[PermissionUpdate] = field(default_factory=list)
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `signal` | `Any \| None` | Reserved for future abort signal support |
|
||
| `suggestions` | `list[PermissionUpdate]` | Permission update suggestions from the CLI |
|
||
|
||
### PermissionResult
|
||
|
||
Union type for permission callback results.
|
||
|
||
```python
|
||
PermissionResult = PermissionResultAllow | PermissionResultDeny
|
||
```
|
||
|
||
### PermissionResultAllow
|
||
|
||
Result indicating the tool call should be allowed.
|
||
|
||
```python
|
||
@dataclass
|
||
class PermissionResultAllow:
|
||
behavior: Literal["allow"] = "allow"
|
||
updated_input: dict[str, Any] | None = None
|
||
updated_permissions: list[PermissionUpdate] | None = None
|
||
```
|
||
|
||
| Field | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `behavior` | `Literal["allow"]` | `"allow"` | Must be “allow” |
|
||
| `updated_input` | `dict[str, Any] \| None` | `None` | Modified input to use instead of original |
|
||
| `updated_permissions` | `list[PermissionUpdate] \| None` | `None` | Permission updates to apply |
|
||
|
||
### PermissionResultDeny
|
||
|
||
Result indicating the tool call should be denied.
|
||
|
||
```python
|
||
@dataclass
|
||
class PermissionResultDeny:
|
||
behavior: Literal["deny"] = "deny"
|
||
message: str = ""
|
||
interrupt: bool = False
|
||
```
|
||
|
||
| Field | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `behavior` | `Literal["deny"]` | `"deny"` | Must be “deny” |
|
||
| `message` | `str` | `""` | Message explaining why the tool was denied |
|
||
| `interrupt` | `bool` | `False` | Whether to interrupt the current execution |
|
||
|
||
### PermissionUpdate
|
||
|
||
Configuration for updating permissions programmatically.
|
||
|
||
```python
|
||
@dataclass
|
||
class PermissionUpdate:
|
||
type: Literal[
|
||
"addRules",
|
||
"replaceRules",
|
||
"removeRules",
|
||
"setMode",
|
||
"addDirectories",
|
||
"removeDirectories",
|
||
]
|
||
rules: list[PermissionRuleValue] | None = None
|
||
behavior: Literal["allow", "deny", "ask"] | None = None
|
||
mode: PermissionMode | None = None
|
||
directories: list[str] | None = None
|
||
destination: (
|
||
Literal["userSettings", "projectSettings", "localSettings", "session"] | None
|
||
) = None
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `type` | `Literal[...]` | The type of permission update operation |
|
||
| `rules` | `list[PermissionRuleValue] \| None` | Rules for add/replace/remove operations |
|
||
| `behavior` | `Literal["allow", "deny", "ask"] \| None` | Behavior for rule-based operations |
|
||
| `mode` | `PermissionMode \| None` | Mode for setMode operation |
|
||
| `directories` | `list[str] \| None` | Directories for add/remove directory operations |
|
||
| `destination` | `Literal[...] \| None` | Where to apply the permission update |
|
||
|
||
### PermissionRuleValue
|
||
|
||
A rule to add, replace, or remove in a permission update.
|
||
|
||
```python
|
||
@dataclass
|
||
class PermissionRuleValue:
|
||
tool_name: str
|
||
rule_content: str | None = None
|
||
```
|
||
|
||
### ToolsPreset
|
||
|
||
Preset tools configuration for using Claude Code’s default tool set.
|
||
|
||
```python
|
||
class ToolsPreset(TypedDict):
|
||
type: Literal["preset"]
|
||
preset: Literal["claude_code"]
|
||
```
|
||
|
||
### ThinkingConfig
|
||
|
||
Controls extended thinking behavior. A union of three configurations:
|
||
|
||
```python
|
||
class ThinkingConfigAdaptive(TypedDict):
|
||
type: Literal["adaptive"]
|
||
|
||
class ThinkingConfigEnabled(TypedDict):
|
||
type: Literal["enabled"]
|
||
budget_tokens: int
|
||
|
||
class ThinkingConfigDisabled(TypedDict):
|
||
type: Literal["disabled"]
|
||
|
||
ThinkingConfig = ThinkingConfigAdaptive | ThinkingConfigEnabled | ThinkingConfigDisabled
|
||
```
|
||
|
||
| Variant | Fields | Description |
|
||
| --- | --- | --- |
|
||
| `adaptive` | `type` | Claude adaptively decides when to think |
|
||
| `enabled` | `type`, `budget_tokens` | Enable thinking with a specific token budget |
|
||
| `disabled` | `type` | Disable thinking |
|
||
|
||
Because these are `TypedDict` classes, they’re plain dicts at runtime. Either construct them as dict literals or call the class like a constructor; both produce a `dict`. Access fields with `config["budget_tokens"]`, not `config.budget_tokens`:
|
||
|
||
```python
|
||
from claude_agent_sdk import ClaudeAgentOptions, ThinkingConfigEnabled
|
||
|
||
# Option 1: dict literal (recommended, no import needed)
|
||
options = ClaudeAgentOptions(thinking={"type": "enabled", "budget_tokens": 20000})
|
||
|
||
# Option 2: constructor-style (returns a plain dict)
|
||
config = ThinkingConfigEnabled(type="enabled", budget_tokens=20000)
|
||
print(config["budget_tokens"]) # 20000
|
||
# config.budget_tokens would raise AttributeError
|
||
```
|
||
|
||
### SdkBeta
|
||
|
||
Literal type for SDK beta features.
|
||
|
||
```python
|
||
SdkBeta = Literal["context-1m-2025-08-07"]
|
||
```
|
||
|
||
Use with the `betas` field in `ClaudeAgentOptions` to enable beta features.
|
||
|
||
The `context-1m-2025-08-07` beta is retired as of April 30, 2026. Passing this header with Claude Sonnet 4.5 or Sonnet 4 has no effect, and requests that exceed the standard 200k-token context window return an error. To use a 1M-token context window, migrate to [Claude Sonnet 4.6, Claude Opus 4.6, or Claude Opus 4.7](https://platform.claude.com/docs/en/about-claude/models/overview), which include 1M context at standard pricing with no beta header required.
|
||
|
||
### McpSdkServerConfig
|
||
|
||
Configuration for SDK MCP servers created with `create_sdk_mcp_server()`.
|
||
|
||
```python
|
||
class McpSdkServerConfig(TypedDict):
|
||
type: Literal["sdk"]
|
||
name: str
|
||
instance: Any # MCP Server instance
|
||
```
|
||
|
||
### McpServerConfig
|
||
|
||
Union type for MCP server configurations.
|
||
|
||
```python
|
||
McpServerConfig = (
|
||
McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig
|
||
)
|
||
```
|
||
|
||
#### McpStdioServerConfig
|
||
|
||
```python
|
||
class McpStdioServerConfig(TypedDict):
|
||
type: NotRequired[Literal["stdio"]] # Optional for backwards compatibility
|
||
command: str
|
||
args: NotRequired[list[str]]
|
||
env: NotRequired[dict[str, str]]
|
||
```
|
||
|
||
#### McpSSEServerConfig
|
||
|
||
```python
|
||
class McpSSEServerConfig(TypedDict):
|
||
type: Literal["sse"]
|
||
url: str
|
||
headers: NotRequired[dict[str, str]]
|
||
```
|
||
|
||
#### McpHttpServerConfig
|
||
|
||
```python
|
||
class McpHttpServerConfig(TypedDict):
|
||
type: Literal["http"]
|
||
url: str
|
||
headers: NotRequired[dict[str, str]]
|
||
```
|
||
|
||
### McpServerStatusConfig
|
||
|
||
The configuration of an MCP server as reported by [`get_mcp_status()`](#methods). This is the union of all [`McpServerConfig`](#mcp-server-config) transport variants plus an output-only `claudeai-proxy` variant for servers proxied through claude.ai.
|
||
|
||
```python
|
||
McpServerStatusConfig = (
|
||
McpStdioServerConfig
|
||
| McpSSEServerConfig
|
||
| McpHttpServerConfig
|
||
| McpSdkServerConfigStatus
|
||
| McpClaudeAIProxyServerConfig
|
||
)
|
||
```
|
||
|
||
`McpSdkServerConfigStatus` is the serializable form of [`McpSdkServerConfig`](#mcp-sdk-server-config) with only `type` (`"sdk"`) and `name` (`str`) fields; the in-process `instance` is omitted. `McpClaudeAIProxyServerConfig` has `type` (`"claudeai-proxy"`), `url` (`str`), and `id` (`str`) fields.
|
||
|
||
### McpStatusResponse
|
||
|
||
Response from [`ClaudeSDKClient.get_mcp_status()`](#methods). Wraps the list of server statuses under the `mcpServers` key.
|
||
|
||
```python
|
||
class McpStatusResponse(TypedDict):
|
||
mcpServers: list[McpServerStatus]
|
||
```
|
||
|
||
### McpServerStatus
|
||
|
||
Status of a connected MCP server, contained in [`McpStatusResponse`](#mcp-status-response).
|
||
|
||
```python
|
||
class McpServerStatus(TypedDict):
|
||
name: str
|
||
status: McpServerConnectionStatus # "connected" | "failed" | "needs-auth" | "pending" | "disabled"
|
||
serverInfo: NotRequired[McpServerInfo]
|
||
error: NotRequired[str]
|
||
config: NotRequired[McpServerStatusConfig]
|
||
scope: NotRequired[str]
|
||
tools: NotRequired[list[McpToolInfo]]
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `name` | `str` | Server name |
|
||
| `status` | `str` | One of `"connected"`, `"failed"`, `"needs-auth"`, `"pending"`, or `"disabled"` |
|
||
| `serverInfo` | `dict` (optional) | Server name and version (`{"name": str, "version": str}`) |
|
||
| `error` | `str` (optional) | Error message if the server failed to connect |
|
||
| `config` | [`McpServerStatusConfig`](#mcp-server-status-config) (optional) | Server configuration. Same shape as [`McpServerConfig`](#mcp-server-config) (stdio, SSE, HTTP, or SDK), plus a `claudeai-proxy` variant for servers connected through claude.ai |
|
||
| `scope` | `str` (optional) | Configuration scope |
|
||
| `tools` | `list` (optional) | Tools provided by this server, each with `name`, `description`, and `annotations` fields |
|
||
|
||
### SdkPluginConfig
|
||
|
||
Configuration for loading plugins in the SDK.
|
||
|
||
```python
|
||
class SdkPluginConfig(TypedDict):
|
||
type: Literal["local"]
|
||
path: str
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `type` | `Literal["local"]` | Must be `"local"` (only local plugins currently supported) |
|
||
| `path` | `str` | Absolute or relative path to the plugin directory |
|
||
|
||
**Example:**
|
||
|
||
```python
|
||
plugins = [
|
||
{"type": "local", "path": "./my-plugin"},
|
||
{"type": "local", "path": "/absolute/path/to/plugin"},
|
||
]
|
||
```
|
||
|
||
For complete information on creating and using plugins, see [Plugins](https://code.claude.com/docs/en/agent-sdk/plugins).
|
||
|
||
## Message Types
|
||
|
||
### Message
|
||
|
||
Union type of all possible messages.
|
||
|
||
```python
|
||
Message = (
|
||
UserMessage
|
||
| AssistantMessage
|
||
| SystemMessage
|
||
| ResultMessage
|
||
| StreamEvent
|
||
| RateLimitEvent
|
||
)
|
||
```
|
||
|
||
### UserMessage
|
||
|
||
User input message.
|
||
|
||
```python
|
||
@dataclass
|
||
class UserMessage:
|
||
content: str | list[ContentBlock]
|
||
uuid: str | None = None
|
||
parent_tool_use_id: str | None = None
|
||
tool_use_result: dict[str, Any] | None = None
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `content` | `str \| list[ContentBlock]` | Message content as text or content blocks |
|
||
| `uuid` | `str \| None` | Unique message identifier |
|
||
| `parent_tool_use_id` | `str \| None` | Tool use ID if this message is a tool result response |
|
||
| `tool_use_result` | `dict[str, Any] \| None` | Tool result data if applicable |
|
||
|
||
### AssistantMessage
|
||
|
||
Assistant response message with content blocks.
|
||
|
||
```python
|
||
@dataclass
|
||
class AssistantMessage:
|
||
content: list[ContentBlock]
|
||
model: str
|
||
parent_tool_use_id: str | None = None
|
||
error: AssistantMessageError | None = None
|
||
usage: dict[str, Any] | None = None
|
||
message_id: str | None = None
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `content` | `list[ContentBlock]` | List of content blocks in the response |
|
||
| `model` | `str` | Model that generated the response |
|
||
| `parent_tool_use_id` | `str \| None` | Tool use ID if this is a nested response |
|
||
| `error` | [`AssistantMessageError`](#assistant-message-error) ` \| None` | Error type if the response encountered an error |
|
||
| `usage` | `dict[str, Any] \| None` | Per-message token usage (same keys as [`ResultMessage.usage`](#result-message)) |
|
||
| `message_id` | `str \| None` | API message ID. Multiple messages from one turn share the same ID |
|
||
|
||
### AssistantMessageError
|
||
|
||
Possible error types for assistant messages.
|
||
|
||
```python
|
||
AssistantMessageError = Literal[
|
||
"authentication_failed",
|
||
"billing_error",
|
||
"rate_limit",
|
||
"invalid_request",
|
||
"server_error",
|
||
"max_output_tokens",
|
||
"unknown",
|
||
]
|
||
```
|
||
|
||
### SystemMessage
|
||
|
||
System message with metadata.
|
||
|
||
```python
|
||
@dataclass
|
||
class SystemMessage:
|
||
subtype: str
|
||
data: dict[str, Any]
|
||
```
|
||
|
||
### ResultMessage
|
||
|
||
Final result message with cost and usage information.
|
||
|
||
```python
|
||
@dataclass
|
||
class ResultMessage:
|
||
subtype: str
|
||
duration_ms: int
|
||
duration_api_ms: int
|
||
is_error: bool
|
||
num_turns: int
|
||
session_id: str
|
||
total_cost_usd: float | None = None
|
||
usage: dict[str, Any] | None = None
|
||
result: str | None = None
|
||
stop_reason: str | None = None
|
||
structured_output: Any = None
|
||
model_usage: dict[str, Any] | None = None
|
||
```
|
||
|
||
The `usage` dict contains the following keys when present:
|
||
|
||
| Key | Type | Description |
|
||
| --- | --- | --- |
|
||
| `input_tokens` | `int` | Total input tokens consumed. |
|
||
| `output_tokens` | `int` | Total output tokens generated. |
|
||
| `cache_creation_input_tokens` | `int` | Tokens used to create new cache entries. |
|
||
| `cache_read_input_tokens` | `int` | Tokens read from existing cache entries. |
|
||
|
||
The `model_usage` dict maps model names to per-model usage. The inner dict keys use camelCase because the value is passed through unmodified from the underlying CLI process, matching the TypeScript [`ModelUsage`](https://code.claude.com/docs/en/agent-sdk/typescript#model-usage) type:
|
||
|
||
| Key | Type | Description |
|
||
| --- | --- | --- |
|
||
| `inputTokens` | `int` | Input tokens for this model. |
|
||
| `outputTokens` | `int` | Output tokens for this model. |
|
||
| `cacheReadInputTokens` | `int` | Cache read tokens for this model. |
|
||
| `cacheCreationInputTokens` | `int` | Cache creation tokens for this model. |
|
||
| `webSearchRequests` | `int` | Web search requests made by this model. |
|
||
| `costUSD` | `float` | Estimated cost in USD for this model, computed client-side. See [Track cost and usage](https://code.claude.com/docs/en/agent-sdk/cost-tracking) for billing caveats. |
|
||
| `contextWindow` | `int` | Context window size for this model. |
|
||
| `maxOutputTokens` | `int` | Maximum output token limit for this model. |
|
||
|
||
### StreamEvent
|
||
|
||
Stream event for partial message updates during streaming. Only received when `include_partial_messages=True` in `ClaudeAgentOptions`. Import via `from claude_agent_sdk.types import StreamEvent`.
|
||
|
||
```python
|
||
@dataclass
|
||
class StreamEvent:
|
||
uuid: str
|
||
session_id: str
|
||
event: dict[str, Any] # The raw Claude API stream event
|
||
parent_tool_use_id: str | None = None
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `uuid` | `str` | Unique identifier for this event |
|
||
| `session_id` | `str` | Session identifier |
|
||
| `event` | `dict[str, Any]` | The raw Claude API stream event data |
|
||
| `parent_tool_use_id` | `str \| None` | Parent tool use ID if this event is from a subagent |
|
||
|
||
### RateLimitEvent
|
||
|
||
Emitted when rate limit status changes (for example, from `"allowed"` to `"allowed_warning"`). Use this to warn users before they hit a hard limit, or to back off when status is `"rejected"`.
|
||
|
||
```python
|
||
@dataclass
|
||
class RateLimitEvent:
|
||
rate_limit_info: RateLimitInfo
|
||
uuid: str
|
||
session_id: str
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `rate_limit_info` | [`RateLimitInfo`](#rate-limit-info) | Current rate limit state |
|
||
| `uuid` | `str` | Unique event identifier |
|
||
| `session_id` | `str` | Session identifier |
|
||
|
||
### RateLimitInfo
|
||
|
||
Rate limit state carried by [`RateLimitEvent`](#rate-limit-event).
|
||
|
||
```python
|
||
RateLimitStatus = Literal["allowed", "allowed_warning", "rejected"]
|
||
RateLimitType = Literal[
|
||
"five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet", "overage"
|
||
]
|
||
|
||
@dataclass
|
||
class RateLimitInfo:
|
||
status: RateLimitStatus
|
||
resets_at: int | None = None
|
||
rate_limit_type: RateLimitType | None = None
|
||
utilization: float | None = None
|
||
overage_status: RateLimitStatus | None = None
|
||
overage_resets_at: int | None = None
|
||
overage_disabled_reason: str | None = None
|
||
raw: dict[str, Any] = field(default_factory=dict)
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `status` | `RateLimitStatus` | Current status. `"allowed_warning"` means approaching the limit; `"rejected"` means the limit was hit |
|
||
| `resets_at` | `int \| None` | Unix timestamp when the rate limit window resets |
|
||
| `rate_limit_type` | `RateLimitType \| None` | Which rate limit window applies |
|
||
| `utilization` | `float \| None` | Fraction of the rate limit consumed (0.0 to 1.0) |
|
||
| `overage_status` | `RateLimitStatus \| None` | Status of pay-as-you-go overage usage, if applicable |
|
||
| `overage_resets_at` | `int \| None` | Unix timestamp when the overage window resets |
|
||
| `overage_disabled_reason` | `str \| None` | Why overage is unavailable, if status is `"rejected"` |
|
||
| `raw` | `dict[str, Any]` | Full raw dict from the CLI, including fields not modeled above |
|
||
|
||
### TaskStartedMessage
|
||
|
||
Emitted when a background task starts. A background task is anything tracked outside the main turn: a backgrounded Bash command, a [Monitor](#monitor) watch, a subagent spawned via the Agent tool, or a remote agent. The `task_type` field tells you which. This naming is unrelated to the `Task` -to- `Agent` tool rename.
|
||
|
||
```python
|
||
@dataclass
|
||
class TaskStartedMessage(SystemMessage):
|
||
task_id: str
|
||
description: str
|
||
uuid: str
|
||
session_id: str
|
||
tool_use_id: str | None = None
|
||
task_type: str | None = None
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `task_id` | `str` | Unique identifier for the task |
|
||
| `description` | `str` | Description of the task |
|
||
| `uuid` | `str` | Unique message identifier |
|
||
| `session_id` | `str` | Session identifier |
|
||
| `tool_use_id` | `str \| None` | Associated tool use ID |
|
||
| `task_type` | `str \| None` | Which kind of background task: `"local_bash"` for background Bash and Monitor watches, `"local_agent"`, or `"remote_agent"` |
|
||
|
||
### TaskUsage
|
||
|
||
Token and timing data for a background task.
|
||
|
||
```python
|
||
class TaskUsage(TypedDict):
|
||
total_tokens: int
|
||
tool_uses: int
|
||
duration_ms: int
|
||
```
|
||
|
||
### TaskProgressMessage
|
||
|
||
Emitted periodically with progress updates for a running background task.
|
||
|
||
```python
|
||
@dataclass
|
||
class TaskProgressMessage(SystemMessage):
|
||
task_id: str
|
||
description: str
|
||
usage: TaskUsage
|
||
uuid: str
|
||
session_id: str
|
||
tool_use_id: str | None = None
|
||
last_tool_name: str | None = None
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `task_id` | `str` | Unique identifier for the task |
|
||
| `description` | `str` | Current status description |
|
||
| `usage` | `TaskUsage` | Token usage for this task so far |
|
||
| `uuid` | `str` | Unique message identifier |
|
||
| `session_id` | `str` | Session identifier |
|
||
| `tool_use_id` | `str \| None` | Associated tool use ID |
|
||
| `last_tool_name` | `str \| None` | Name of the last tool the task used |
|
||
|
||
### TaskNotificationMessage
|
||
|
||
Emitted when a background task completes, fails, or is stopped. Background tasks include `run_in_background` Bash commands, Monitor watches, and background subagents.
|
||
|
||
```python
|
||
@dataclass
|
||
class TaskNotificationMessage(SystemMessage):
|
||
task_id: str
|
||
status: TaskNotificationStatus # "completed" | "failed" | "stopped"
|
||
output_file: str
|
||
summary: str
|
||
uuid: str
|
||
session_id: str
|
||
tool_use_id: str | None = None
|
||
usage: TaskUsage | None = None
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `task_id` | `str` | Unique identifier for the task |
|
||
| `status` | `TaskNotificationStatus` | One of `"completed"`, `"failed"`, or `"stopped"` |
|
||
| `output_file` | `str` | Path to the task output file |
|
||
| `summary` | `str` | Summary of the task result |
|
||
| `uuid` | `str` | Unique message identifier |
|
||
| `session_id` | `str` | Session identifier |
|
||
| `tool_use_id` | `str \| None` | Associated tool use ID |
|
||
| `usage` | `TaskUsage \| None` | Final token usage for the task |
|
||
|
||
## Content Block Types
|
||
|
||
### ContentBlock
|
||
|
||
Union type of all content blocks.
|
||
|
||
```python
|
||
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock
|
||
```
|
||
|
||
### TextBlock
|
||
|
||
Text content block.
|
||
|
||
```python
|
||
@dataclass
|
||
class TextBlock:
|
||
text: str
|
||
```
|
||
|
||
### ThinkingBlock
|
||
|
||
Thinking content block (for models with thinking capability).
|
||
|
||
```python
|
||
@dataclass
|
||
class ThinkingBlock:
|
||
thinking: str
|
||
signature: str
|
||
```
|
||
|
||
### ToolUseBlock
|
||
|
||
Tool use request block.
|
||
|
||
```python
|
||
@dataclass
|
||
class ToolUseBlock:
|
||
id: str
|
||
name: str
|
||
input: dict[str, Any]
|
||
```
|
||
|
||
### ToolResultBlock
|
||
|
||
Tool execution result block.
|
||
|
||
```python
|
||
@dataclass
|
||
class ToolResultBlock:
|
||
tool_use_id: str
|
||
content: str | list[dict[str, Any]] | None = None
|
||
is_error: bool | None = None
|
||
```
|
||
|
||
## Error Types
|
||
|
||
### ClaudeSDKError
|
||
|
||
Base exception class for all SDK errors.
|
||
|
||
```python
|
||
class ClaudeSDKError(Exception):
|
||
"""Base error for Claude SDK."""
|
||
```
|
||
|
||
### CLINotFoundError
|
||
|
||
Raised when Claude Code CLI is not installed or not found.
|
||
|
||
```python
|
||
class CLINotFoundError(CLIConnectionError):
|
||
def __init__(
|
||
self, message: str = "Claude Code not found", cli_path: str | None = None
|
||
):
|
||
"""
|
||
Args:
|
||
message: Error message (default: "Claude Code not found")
|
||
cli_path: Optional path to the CLI that was not found
|
||
"""
|
||
```
|
||
|
||
### CLIConnectionError
|
||
|
||
Raised when connection to Claude Code fails.
|
||
|
||
```python
|
||
class CLIConnectionError(ClaudeSDKError):
|
||
"""Failed to connect to Claude Code."""
|
||
```
|
||
|
||
### ProcessError
|
||
|
||
Raised when the Claude Code process fails.
|
||
|
||
```python
|
||
class ProcessError(ClaudeSDKError):
|
||
def __init__(
|
||
self, message: str, exit_code: int | None = None, stderr: str | None = None
|
||
):
|
||
self.exit_code = exit_code
|
||
self.stderr = stderr
|
||
```
|
||
|
||
### CLIJSONDecodeError
|
||
|
||
Raised when JSON parsing fails.
|
||
|
||
```python
|
||
class CLIJSONDecodeError(ClaudeSDKError):
|
||
def __init__(self, line: str, original_error: Exception):
|
||
"""
|
||
Args:
|
||
line: The line that failed to parse
|
||
original_error: The original JSON decode exception
|
||
"""
|
||
self.line = line
|
||
self.original_error = original_error
|
||
```
|
||
|
||
## Hook Types
|
||
|
||
For a comprehensive guide on using hooks with examples and common patterns, see the [Hooks guide](https://code.claude.com/docs/en/agent-sdk/hooks).
|
||
|
||
### HookEvent
|
||
|
||
Supported hook event types.
|
||
|
||
```python
|
||
HookEvent = Literal[
|
||
"PreToolUse", # Called before tool execution
|
||
"PostToolUse", # Called after tool execution
|
||
"PostToolUseFailure", # Called when a tool execution fails
|
||
"UserPromptSubmit", # Called when user submits a prompt
|
||
"Stop", # Called when stopping execution
|
||
"SubagentStop", # Called when a subagent stops
|
||
"PreCompact", # Called before message compaction
|
||
"Notification", # Called for notification events
|
||
"SubagentStart", # Called when a subagent starts
|
||
"PermissionRequest", # Called when a permission decision is needed
|
||
]
|
||
```
|
||
|
||
The TypeScript SDK supports additional hook events not yet available in Python: `SessionStart`, `SessionEnd`, `Setup`, `TeammateIdle`, `TaskCompleted`, `ConfigChange`, `WorktreeCreate`, and `WorktreeRemove`.
|
||
|
||
### HookCallback
|
||
|
||
Type definition for hook callback functions.
|
||
|
||
```python
|
||
HookCallback = Callable[[HookInput, str | None, HookContext], Awaitable[HookJSONOutput]]
|
||
```
|
||
|
||
Parameters:
|
||
|
||
- `input`: Strongly-typed hook input with discriminated unions based on `hook_event_name` (see [`HookInput`](#hook-input))
|
||
- `tool_use_id`: Optional tool use identifier (for tool-related hooks)
|
||
- `context`: Hook context with additional information
|
||
|
||
Returns a [`HookJSONOutput`](#hook-json-output) that may contain:
|
||
|
||
- `decision`: `"block"` to block the action
|
||
- `systemMessage`: System message to add to the transcript
|
||
- `hookSpecificOutput`: Hook-specific output data
|
||
|
||
### HookContext
|
||
|
||
Context information passed to hook callbacks.
|
||
|
||
```python
|
||
class HookContext(TypedDict):
|
||
signal: Any | None # Future: abort signal support
|
||
```
|
||
|
||
### HookMatcher
|
||
|
||
Configuration for matching hooks to specific events or tools.
|
||
|
||
```python
|
||
@dataclass
|
||
class HookMatcher:
|
||
matcher: str | None = (
|
||
None # Tool name or pattern to match (e.g., "Bash", "Write|Edit")
|
||
)
|
||
hooks: list[HookCallback] = field(
|
||
default_factory=list
|
||
) # List of callbacks to execute
|
||
timeout: float | None = (
|
||
None # Timeout in seconds for all hooks in this matcher (default: 60)
|
||
)
|
||
```
|
||
|
||
### HookInput
|
||
|
||
Union type of all hook input types. The actual type depends on the `hook_event_name` field.
|
||
|
||
```python
|
||
HookInput = (
|
||
PreToolUseHookInput
|
||
| PostToolUseHookInput
|
||
| PostToolUseFailureHookInput
|
||
| UserPromptSubmitHookInput
|
||
| StopHookInput
|
||
| SubagentStopHookInput
|
||
| PreCompactHookInput
|
||
| NotificationHookInput
|
||
| SubagentStartHookInput
|
||
| PermissionRequestHookInput
|
||
)
|
||
```
|
||
|
||
### BaseHookInput
|
||
|
||
Base fields present in all hook input types.
|
||
|
||
```python
|
||
class BaseHookInput(TypedDict):
|
||
session_id: str
|
||
transcript_path: str
|
||
cwd: str
|
||
permission_mode: NotRequired[str]
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `session_id` | `str` | Current session identifier |
|
||
| `transcript_path` | `str` | Path to the session transcript file |
|
||
| `cwd` | `str` | Current working directory |
|
||
| `permission_mode` | `str` (optional) | Current permission mode |
|
||
|
||
### PreToolUseHookInput
|
||
|
||
Input data for `PreToolUse` hook events.
|
||
|
||
```python
|
||
class PreToolUseHookInput(BaseHookInput):
|
||
hook_event_name: Literal["PreToolUse"]
|
||
tool_name: str
|
||
tool_input: dict[str, Any]
|
||
tool_use_id: str
|
||
agent_id: NotRequired[str]
|
||
agent_type: NotRequired[str]
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `hook_event_name` | `Literal["PreToolUse"]` | Always “PreToolUse” |
|
||
| `tool_name` | `str` | Name of the tool about to be executed |
|
||
| `tool_input` | `dict[str, Any]` | Input parameters for the tool |
|
||
| `tool_use_id` | `str` | Unique identifier for this tool use |
|
||
| `agent_id` | `str` (optional) | Subagent identifier, present when the hook fires inside a subagent |
|
||
| `agent_type` | `str` (optional) | Subagent type, present when the hook fires inside a subagent |
|
||
|
||
### PostToolUseHookInput
|
||
|
||
Input data for `PostToolUse` hook events.
|
||
|
||
```python
|
||
class PostToolUseHookInput(BaseHookInput):
|
||
hook_event_name: Literal["PostToolUse"]
|
||
tool_name: str
|
||
tool_input: dict[str, Any]
|
||
tool_response: Any
|
||
tool_use_id: str
|
||
agent_id: NotRequired[str]
|
||
agent_type: NotRequired[str]
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `hook_event_name` | `Literal["PostToolUse"]` | Always “PostToolUse” |
|
||
| `tool_name` | `str` | Name of the tool that was executed |
|
||
| `tool_input` | `dict[str, Any]` | Input parameters that were used |
|
||
| `tool_response` | `Any` | Response from the tool execution |
|
||
| `tool_use_id` | `str` | Unique identifier for this tool use |
|
||
| `agent_id` | `str` (optional) | Subagent identifier, present when the hook fires inside a subagent |
|
||
| `agent_type` | `str` (optional) | Subagent type, present when the hook fires inside a subagent |
|
||
|
||
### PostToolUseFailureHookInput
|
||
|
||
Input data for `PostToolUseFailure` hook events. Called when a tool execution fails.
|
||
|
||
```python
|
||
class PostToolUseFailureHookInput(BaseHookInput):
|
||
hook_event_name: Literal["PostToolUseFailure"]
|
||
tool_name: str
|
||
tool_input: dict[str, Any]
|
||
tool_use_id: str
|
||
error: str
|
||
is_interrupt: NotRequired[bool]
|
||
agent_id: NotRequired[str]
|
||
agent_type: NotRequired[str]
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `hook_event_name` | `Literal["PostToolUseFailure"]` | Always “PostToolUseFailure” |
|
||
| `tool_name` | `str` | Name of the tool that failed |
|
||
| `tool_input` | `dict[str, Any]` | Input parameters that were used |
|
||
| `tool_use_id` | `str` | Unique identifier for this tool use |
|
||
| `error` | `str` | Error message from the failed execution |
|
||
| `is_interrupt` | `bool` (optional) | Whether the failure was caused by an interrupt |
|
||
| `agent_id` | `str` (optional) | Subagent identifier, present when the hook fires inside a subagent |
|
||
| `agent_type` | `str` (optional) | Subagent type, present when the hook fires inside a subagent |
|
||
|
||
### UserPromptSubmitHookInput
|
||
|
||
Input data for `UserPromptSubmit` hook events.
|
||
|
||
```python
|
||
class UserPromptSubmitHookInput(BaseHookInput):
|
||
hook_event_name: Literal["UserPromptSubmit"]
|
||
prompt: str
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `hook_event_name` | `Literal["UserPromptSubmit"]` | Always “UserPromptSubmit” |
|
||
| `prompt` | `str` | The user’s submitted prompt |
|
||
|
||
### StopHookInput
|
||
|
||
Input data for `Stop` hook events.
|
||
|
||
```python
|
||
class StopHookInput(BaseHookInput):
|
||
hook_event_name: Literal["Stop"]
|
||
stop_hook_active: bool
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `hook_event_name` | `Literal["Stop"]` | Always “Stop” |
|
||
| `stop_hook_active` | `bool` | Whether the stop hook is active |
|
||
|
||
### SubagentStopHookInput
|
||
|
||
Input data for `SubagentStop` hook events.
|
||
|
||
```python
|
||
class SubagentStopHookInput(BaseHookInput):
|
||
hook_event_name: Literal["SubagentStop"]
|
||
stop_hook_active: bool
|
||
agent_id: str
|
||
agent_transcript_path: str
|
||
agent_type: str
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `hook_event_name` | `Literal["SubagentStop"]` | Always “SubagentStop” |
|
||
| `stop_hook_active` | `bool` | Whether the stop hook is active |
|
||
| `agent_id` | `str` | Unique identifier for the subagent |
|
||
| `agent_transcript_path` | `str` | Path to the subagent’s transcript file |
|
||
| `agent_type` | `str` | Type of the subagent |
|
||
|
||
### PreCompactHookInput
|
||
|
||
Input data for `PreCompact` hook events.
|
||
|
||
```python
|
||
class PreCompactHookInput(BaseHookInput):
|
||
hook_event_name: Literal["PreCompact"]
|
||
trigger: Literal["manual", "auto"]
|
||
custom_instructions: str | None
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `hook_event_name` | `Literal["PreCompact"]` | Always “PreCompact” |
|
||
| `trigger` | `Literal["manual", "auto"]` | What triggered the compaction |
|
||
| `custom_instructions` | `str \| None` | Custom instructions for compaction |
|
||
|
||
### NotificationHookInput
|
||
|
||
Input data for `Notification` hook events.
|
||
|
||
```python
|
||
class NotificationHookInput(BaseHookInput):
|
||
hook_event_name: Literal["Notification"]
|
||
message: str
|
||
title: NotRequired[str]
|
||
notification_type: str
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `hook_event_name` | `Literal["Notification"]` | Always “Notification” |
|
||
| `message` | `str` | Notification message content |
|
||
| `title` | `str` (optional) | Notification title |
|
||
| `notification_type` | `str` | Type of notification |
|
||
|
||
### SubagentStartHookInput
|
||
|
||
Input data for `SubagentStart` hook events.
|
||
|
||
```python
|
||
class SubagentStartHookInput(BaseHookInput):
|
||
hook_event_name: Literal["SubagentStart"]
|
||
agent_id: str
|
||
agent_type: str
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `hook_event_name` | `Literal["SubagentStart"]` | Always “SubagentStart” |
|
||
| `agent_id` | `str` | Unique identifier for the subagent |
|
||
| `agent_type` | `str` | Type of the subagent |
|
||
|
||
### PermissionRequestHookInput
|
||
|
||
Input data for `PermissionRequest` hook events. Allows hooks to handle permission decisions programmatically.
|
||
|
||
```python
|
||
class PermissionRequestHookInput(BaseHookInput):
|
||
hook_event_name: Literal["PermissionRequest"]
|
||
tool_name: str
|
||
tool_input: dict[str, Any]
|
||
permission_suggestions: NotRequired[list[Any]]
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
| --- | --- | --- |
|
||
| `hook_event_name` | `Literal["PermissionRequest"]` | Always “PermissionRequest” |
|
||
| `tool_name` | `str` | Name of the tool requesting permission |
|
||
| `tool_input` | `dict[str, Any]` | Input parameters for the tool |
|
||
| `permission_suggestions` | `list[Any]` (optional) | Suggested permission updates from the CLI |
|
||
|
||
### HookJSONOutput
|
||
|
||
Union type for hook callback return values.
|
||
|
||
```python
|
||
HookJSONOutput = AsyncHookJSONOutput | SyncHookJSONOutput
|
||
```
|
||
|
||
#### SyncHookJSONOutput
|
||
|
||
Synchronous hook output with control and decision fields.
|
||
|
||
```python
|
||
class SyncHookJSONOutput(TypedDict):
|
||
# Control fields
|
||
continue_: NotRequired[bool] # Whether to proceed (default: True)
|
||
suppressOutput: NotRequired[bool] # Hide stdout from transcript
|
||
stopReason: NotRequired[str] # Message when continue is False
|
||
|
||
# Decision fields
|
||
decision: NotRequired[Literal["block"]]
|
||
systemMessage: NotRequired[str] # Warning message for user
|
||
reason: NotRequired[str] # Feedback for Claude
|
||
|
||
# Hook-specific output
|
||
hookSpecificOutput: NotRequired[HookSpecificOutput]
|
||
```
|
||
|
||
Use `continue_` (with underscore) in Python code. It is automatically converted to `continue` when sent to the CLI.
|
||
|
||
#### HookSpecificOutput
|
||
|
||
A `TypedDict` containing the hook event name and event-specific fields. The shape depends on the `hookEventName` value. For full details on available fields per hook event, see [Control execution with hooks](https://code.claude.com/docs/en/agent-sdk/hooks#outputs).
|
||
|
||
A discriminated union of event-specific output types. The `hookEventName` field determines which fields are valid.
|
||
|
||
```python
|
||
class PreToolUseHookSpecificOutput(TypedDict):
|
||
hookEventName: Literal["PreToolUse"]
|
||
permissionDecision: NotRequired[Literal["allow", "deny", "ask"]]
|
||
permissionDecisionReason: NotRequired[str]
|
||
updatedInput: NotRequired[dict[str, Any]]
|
||
additionalContext: NotRequired[str]
|
||
|
||
class PostToolUseHookSpecificOutput(TypedDict):
|
||
hookEventName: Literal["PostToolUse"]
|
||
additionalContext: NotRequired[str]
|
||
updatedMCPToolOutput: NotRequired[Any]
|
||
|
||
class PostToolUseFailureHookSpecificOutput(TypedDict):
|
||
hookEventName: Literal["PostToolUseFailure"]
|
||
additionalContext: NotRequired[str]
|
||
|
||
class UserPromptSubmitHookSpecificOutput(TypedDict):
|
||
hookEventName: Literal["UserPromptSubmit"]
|
||
additionalContext: NotRequired[str]
|
||
|
||
class NotificationHookSpecificOutput(TypedDict):
|
||
hookEventName: Literal["Notification"]
|
||
additionalContext: NotRequired[str]
|
||
|
||
class SubagentStartHookSpecificOutput(TypedDict):
|
||
hookEventName: Literal["SubagentStart"]
|
||
additionalContext: NotRequired[str]
|
||
|
||
class PermissionRequestHookSpecificOutput(TypedDict):
|
||
hookEventName: Literal["PermissionRequest"]
|
||
decision: dict[str, Any]
|
||
|
||
HookSpecificOutput = (
|
||
PreToolUseHookSpecificOutput
|
||
| PostToolUseHookSpecificOutput
|
||
| PostToolUseFailureHookSpecificOutput
|
||
| UserPromptSubmitHookSpecificOutput
|
||
| NotificationHookSpecificOutput
|
||
| SubagentStartHookSpecificOutput
|
||
| PermissionRequestHookSpecificOutput
|
||
)
|
||
```
|
||
|
||
#### AsyncHookJSONOutput
|
||
|
||
Async hook output that defers hook execution.
|
||
|
||
```python
|
||
class AsyncHookJSONOutput(TypedDict):
|
||
async_: Literal[True] # Set to True to defer execution
|
||
asyncTimeout: NotRequired[int] # Timeout in milliseconds
|
||
```
|
||
|
||
Use `async_` (with underscore) in Python code. It is automatically converted to `async` when sent to the CLI.
|
||
|
||
### Hook Usage Example
|
||
|
||
This example registers two hooks: one that blocks dangerous bash commands like `rm -rf /`, and another that logs all tool usage for auditing. The security hook only runs on Bash commands (via the `matcher`), while the logging hook runs on all tools.
|
||
|
||
```python
|
||
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher, HookContext
|
||
from typing import Any
|
||
|
||
async def validate_bash_command(
|
||
input_data: dict[str, Any], tool_use_id: str | None, context: HookContext
|
||
) -> dict[str, Any]:
|
||
"""Validate and potentially block dangerous bash commands."""
|
||
if input_data["tool_name"] == "Bash":
|
||
command = input_data["tool_input"].get("command", "")
|
||
if "rm -rf /" in command:
|
||
return {
|
||
"hookSpecificOutput": {
|
||
"hookEventName": "PreToolUse",
|
||
"permissionDecision": "deny",
|
||
"permissionDecisionReason": "Dangerous command blocked",
|
||
}
|
||
}
|
||
return {}
|
||
|
||
async def log_tool_use(
|
||
input_data: dict[str, Any], tool_use_id: str | None, context: HookContext
|
||
) -> dict[str, Any]:
|
||
"""Log all tool usage for auditing."""
|
||
print(f"Tool used: {input_data.get('tool_name')}")
|
||
return {}
|
||
|
||
options = ClaudeAgentOptions(
|
||
hooks={
|
||
"PreToolUse": [
|
||
HookMatcher(
|
||
matcher="Bash", hooks=[validate_bash_command], timeout=120
|
||
), # 2 min for validation
|
||
HookMatcher(
|
||
hooks=[log_tool_use]
|
||
), # Applies to all tools (default 60s timeout)
|
||
],
|
||
"PostToolUse": [HookMatcher(hooks=[log_tool_use])],
|
||
}
|
||
)
|
||
|
||
async for message in query(prompt="Analyze this codebase", options=options):
|
||
print(message)
|
||
```
|
||
|
||
## Tool Input/Output Types
|
||
|
||
Documentation of input/output schemas for all built-in Claude Code tools. While the Python SDK doesn’t export these as types, they represent the structure of tool inputs and outputs in messages.
|
||
|
||
### Agent
|
||
|
||
**Tool name:** `Agent` (previously `Task`, which is still accepted as an alias)
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"description": str, # A short (3-5 word) description of the task
|
||
"prompt": str, # The task for the agent to perform
|
||
"subagent_type": str, # The type of specialized agent to use
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"result": str, # Final result from the subagent
|
||
"usage": dict | None, # Token usage statistics
|
||
"total_cost_usd": float | None, # Estimated total cost in USD
|
||
"duration_ms": int | None, # Execution duration in milliseconds
|
||
}
|
||
```
|
||
|
||
### AskUserQuestion
|
||
|
||
**Tool name:** `AskUserQuestion`
|
||
|
||
Asks the user clarifying questions during execution. See [Handle approvals and user input](https://code.claude.com/docs/en/agent-sdk/user-input#handle-clarifying-questions) for usage details.
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"questions": [ # Questions to ask the user (1-4 questions)
|
||
{
|
||
"question": str, # The complete question to ask the user
|
||
"header": str, # Very short label displayed as a chip/tag (max 12 chars)
|
||
"options": [ # The available choices (2-4 options)
|
||
{
|
||
"label": str, # Display text for this option (1-5 words)
|
||
"description": str, # Explanation of what this option means
|
||
}
|
||
],
|
||
"multiSelect": bool, # Set to true to allow multiple selections
|
||
}
|
||
],
|
||
"answers": dict | None, # User answers populated by the permission system
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"questions": [ # The questions that were asked
|
||
{
|
||
"question": str,
|
||
"header": str,
|
||
"options": [{"label": str, "description": str}],
|
||
"multiSelect": bool,
|
||
}
|
||
],
|
||
"answers": dict[str, str], # Maps question text to answer string
|
||
# Multi-select answers are comma-separated
|
||
}
|
||
```
|
||
|
||
### Bash
|
||
|
||
**Tool name:** `Bash`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"command": str, # The command to execute
|
||
"timeout": int | None, # Optional timeout in milliseconds (max 600000)
|
||
"description": str | None, # Clear, concise description (5-10 words)
|
||
"run_in_background": bool | None, # Set to true to run in background
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"output": str, # Combined stdout and stderr output
|
||
"exitCode": int, # Exit code of the command
|
||
"killed": bool | None, # Whether command was killed due to timeout
|
||
"shellId": str | None, # Shell ID for background processes
|
||
}
|
||
```
|
||
|
||
### Monitor
|
||
|
||
**Tool name:** `Monitor`
|
||
|
||
Runs a background script and delivers each stdout line to Claude as an event so it can react without polling. Monitor follows the same permission rules as Bash. See the [Monitor tool reference](https://code.claude.com/docs/en/tools-reference#monitor-tool) for behavior and provider availability.
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"command": str, # Shell script; each stdout line is an event, exit ends the watch
|
||
"description": str, # Short description shown in notifications
|
||
"timeout_ms": int | None, # Kill after this deadline (default 300000, max 3600000)
|
||
"persistent": bool | None, # Run for the lifetime of the session; stop with TaskStop
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"taskId": str, # ID of the background monitor task
|
||
"timeoutMs": int, # Timeout deadline in milliseconds (0 when persistent)
|
||
"persistent": bool | None, # True when running until TaskStop or session end
|
||
}
|
||
```
|
||
|
||
### Edit
|
||
|
||
**Tool name:** `Edit`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"file_path": str, # The absolute path to the file to modify
|
||
"old_string": str, # The text to replace
|
||
"new_string": str, # The text to replace it with
|
||
"replace_all": bool | None, # Replace all occurrences (default False)
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"message": str, # Confirmation message
|
||
"replacements": int, # Number of replacements made
|
||
"file_path": str, # File path that was edited
|
||
}
|
||
```
|
||
|
||
### Read
|
||
|
||
**Tool name:** `Read`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"file_path": str, # The absolute path to the file to read
|
||
"offset": int | None, # The line number to start reading from
|
||
"limit": int | None, # The number of lines to read
|
||
}
|
||
```
|
||
|
||
**Output (Text files):**
|
||
|
||
```python
|
||
{
|
||
"content": str, # File contents with line numbers
|
||
"total_lines": int, # Total number of lines in file
|
||
"lines_returned": int, # Lines actually returned
|
||
}
|
||
```
|
||
|
||
**Output (Images):**
|
||
|
||
```python
|
||
{
|
||
"image": str, # Base64 encoded image data
|
||
"mime_type": str, # Image MIME type
|
||
"file_size": int, # File size in bytes
|
||
}
|
||
```
|
||
|
||
### Write
|
||
|
||
**Tool name:** `Write`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"file_path": str, # The absolute path to the file to write
|
||
"content": str, # The content to write to the file
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"message": str, # Success message
|
||
"bytes_written": int, # Number of bytes written
|
||
"file_path": str, # File path that was written
|
||
}
|
||
```
|
||
|
||
### Glob
|
||
|
||
**Tool name:** `Glob`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"pattern": str, # The glob pattern to match files against
|
||
"path": str | None, # The directory to search in (defaults to cwd)
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"matches": list[str], # Array of matching file paths
|
||
"count": int, # Number of matches found
|
||
"search_path": str, # Search directory used
|
||
}
|
||
```
|
||
|
||
### Grep
|
||
|
||
**Tool name:** `Grep`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"pattern": str, # The regular expression pattern
|
||
"path": str | None, # File or directory to search in
|
||
"glob": str | None, # Glob pattern to filter files
|
||
"type": str | None, # File type to search
|
||
"output_mode": str | None, # "content", "files_with_matches", or "count"
|
||
"-i": bool | None, # Case insensitive search
|
||
"-n": bool | None, # Show line numbers
|
||
"-B": int | None, # Lines to show before each match
|
||
"-A": int | None, # Lines to show after each match
|
||
"-C": int | None, # Lines to show before and after
|
||
"head_limit": int | None, # Limit output to first N lines/entries
|
||
"multiline": bool | None, # Enable multiline mode
|
||
}
|
||
```
|
||
|
||
**Output (content mode):**
|
||
|
||
```python
|
||
{
|
||
"matches": [
|
||
{
|
||
"file": str,
|
||
"line_number": int | None,
|
||
"line": str,
|
||
"before_context": list[str] | None,
|
||
"after_context": list[str] | None,
|
||
}
|
||
],
|
||
"total_matches": int,
|
||
}
|
||
```
|
||
|
||
**Output (files\_with\_matches mode):**
|
||
|
||
```python
|
||
{
|
||
"files": list[str], # Files containing matches
|
||
"count": int, # Number of files with matches
|
||
}
|
||
```
|
||
|
||
### NotebookEdit
|
||
|
||
**Tool name:** `NotebookEdit`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"notebook_path": str, # Absolute path to the Jupyter notebook
|
||
"cell_id": str | None, # The ID of the cell to edit
|
||
"new_source": str, # The new source for the cell
|
||
"cell_type": "code" | "markdown" | None, # The type of the cell
|
||
"edit_mode": "replace" | "insert" | "delete" | None, # Edit operation type
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"message": str, # Success message
|
||
"edit_type": "replaced" | "inserted" | "deleted", # Type of edit performed
|
||
"cell_id": str | None, # Cell ID that was affected
|
||
"total_cells": int, # Total cells in notebook after edit
|
||
}
|
||
```
|
||
|
||
### WebFetch
|
||
|
||
**Tool name:** `WebFetch`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"url": str, # The URL to fetch content from
|
||
"prompt": str, # The prompt to run on the fetched content
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"response": str, # AI model's response to the prompt
|
||
"url": str, # URL that was fetched
|
||
"final_url": str | None, # Final URL after redirects
|
||
"status_code": int | None, # HTTP status code
|
||
}
|
||
```
|
||
|
||
### WebSearch
|
||
|
||
**Tool name:** `WebSearch`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"query": str, # The search query to use
|
||
"allowed_domains": list[str] | None, # Only include results from these domains
|
||
"blocked_domains": list[str] | None, # Never include results from these domains
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"results": [{"title": str, "url": str, "snippet": str, "metadata": dict | None}],
|
||
"total_results": int,
|
||
"query": str,
|
||
}
|
||
```
|
||
|
||
### TodoWrite
|
||
|
||
**Tool name:** `TodoWrite`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"todos": [
|
||
{
|
||
"content": str, # The task description
|
||
"status": "pending" | "in_progress" | "completed", # Task status
|
||
"activeForm": str, # Active form of the description
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"message": str, # Success message
|
||
"stats": {"total": int, "pending": int, "in_progress": int, "completed": int},
|
||
}
|
||
```
|
||
|
||
### BashOutput
|
||
|
||
**Tool name:** `BashOutput`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"bash_id": str, # The ID of the background shell
|
||
"filter": str | None, # Optional regex to filter output lines
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"output": str, # New output since last check
|
||
"status": "running" | "completed" | "failed", # Current shell status
|
||
"exitCode": int | None, # Exit code when completed
|
||
}
|
||
```
|
||
|
||
### KillBash
|
||
|
||
**Tool name:** `KillBash`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"shell_id": str # The ID of the background shell to kill
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"message": str, # Success message
|
||
"shell_id": str, # ID of the killed shell
|
||
}
|
||
```
|
||
|
||
### ExitPlanMode
|
||
|
||
**Tool name:** `ExitPlanMode`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"plan": str # The plan to run by the user for approval
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"message": str, # Confirmation message
|
||
"approved": bool | None, # Whether user approved the plan
|
||
}
|
||
```
|
||
|
||
### ListMcpResources
|
||
|
||
**Tool name:** `ListMcpResources`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"server": str | None # Optional server name to filter resources by
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"resources": [
|
||
{
|
||
"uri": str,
|
||
"name": str,
|
||
"description": str | None,
|
||
"mimeType": str | None,
|
||
"server": str,
|
||
}
|
||
],
|
||
"total": int,
|
||
}
|
||
```
|
||
|
||
### ReadMcpResource
|
||
|
||
**Tool name:** `ReadMcpResource`
|
||
|
||
**Input:**
|
||
|
||
```python
|
||
{
|
||
"server": str, # The MCP server name
|
||
"uri": str, # The resource URI to read
|
||
}
|
||
```
|
||
|
||
**Output:**
|
||
|
||
```python
|
||
{
|
||
"contents": [
|
||
{"uri": str, "mimeType": str | None, "text": str | None, "blob": str | None}
|
||
],
|
||
"server": str,
|
||
}
|
||
```
|
||
|
||
## Advanced Features with ClaudeSDKClient
|
||
|
||
### Building a Continuous Conversation Interface
|
||
|
||
```python
|
||
from claude_agent_sdk import (
|
||
ClaudeSDKClient,
|
||
ClaudeAgentOptions,
|
||
AssistantMessage,
|
||
TextBlock,
|
||
)
|
||
import asyncio
|
||
|
||
class ConversationSession:
|
||
"""Maintains a single conversation session with Claude."""
|
||
|
||
def __init__(self, options: ClaudeAgentOptions | None = None):
|
||
self.client = ClaudeSDKClient(options)
|
||
self.turn_count = 0
|
||
|
||
async def start(self):
|
||
await self.client.connect()
|
||
print("Starting conversation session. Claude will remember context.")
|
||
print(
|
||
"Commands: 'exit' to quit, 'interrupt' to stop current task, 'new' for new session"
|
||
)
|
||
|
||
while True:
|
||
user_input = input(f"\n[Turn {self.turn_count + 1}] You: ")
|
||
|
||
if user_input.lower() == "exit":
|
||
break
|
||
elif user_input.lower() == "interrupt":
|
||
await self.client.interrupt()
|
||
print("Task interrupted!")
|
||
continue
|
||
elif user_input.lower() == "new":
|
||
# Disconnect and reconnect for a fresh session
|
||
await self.client.disconnect()
|
||
await self.client.connect()
|
||
self.turn_count = 0
|
||
print("Started new conversation session (previous context cleared)")
|
||
continue
|
||
|
||
# Send message - the session retains all previous messages
|
||
await self.client.query(user_input)
|
||
self.turn_count += 1
|
||
|
||
# Process response
|
||
print(f"[Turn {self.turn_count}] Claude: ", end="")
|
||
async for message in self.client.receive_response():
|
||
if isinstance(message, AssistantMessage):
|
||
for block in message.content:
|
||
if isinstance(block, TextBlock):
|
||
print(block.text, end="")
|
||
print() # New line after response
|
||
|
||
await self.client.disconnect()
|
||
print(f"Conversation ended after {self.turn_count} turns.")
|
||
|
||
async def main():
|
||
options = ClaudeAgentOptions(
|
||
allowed_tools=["Read", "Write", "Bash"], permission_mode="acceptEdits"
|
||
)
|
||
session = ConversationSession(options)
|
||
await session.start()
|
||
|
||
# Example conversation:
|
||
# Turn 1 - You: "Create a file called hello.py"
|
||
# Turn 1 - Claude: "I'll create a hello.py file for you..."
|
||
# Turn 2 - You: "What's in that file?"
|
||
# Turn 2 - Claude: "The hello.py file I just created contains..." (remembers!)
|
||
# Turn 3 - You: "Add a main function to it"
|
||
# Turn 3 - Claude: "I'll add a main function to hello.py..." (knows which file!)
|
||
|
||
asyncio.run(main())
|
||
```
|
||
|
||
### Using Hooks for Behavior Modification
|
||
|
||
```python
|
||
from claude_agent_sdk import (
|
||
ClaudeSDKClient,
|
||
ClaudeAgentOptions,
|
||
HookMatcher,
|
||
HookContext,
|
||
)
|
||
import asyncio
|
||
from typing import Any
|
||
|
||
async def pre_tool_logger(
|
||
input_data: dict[str, Any], tool_use_id: str | None, context: HookContext
|
||
) -> dict[str, Any]:
|
||
"""Log all tool usage before execution."""
|
||
tool_name = input_data.get("tool_name", "unknown")
|
||
print(f"[PRE-TOOL] About to use: {tool_name}")
|
||
|
||
# You can modify or block the tool execution here
|
||
if tool_name == "Bash" and "rm -rf" in str(input_data.get("tool_input", {})):
|
||
return {
|
||
"hookSpecificOutput": {
|
||
"hookEventName": "PreToolUse",
|
||
"permissionDecision": "deny",
|
||
"permissionDecisionReason": "Dangerous command blocked",
|
||
}
|
||
}
|
||
return {}
|
||
|
||
async def post_tool_logger(
|
||
input_data: dict[str, Any], tool_use_id: str | None, context: HookContext
|
||
) -> dict[str, Any]:
|
||
"""Log results after tool execution."""
|
||
tool_name = input_data.get("tool_name", "unknown")
|
||
print(f"[POST-TOOL] Completed: {tool_name}")
|
||
return {}
|
||
|
||
async def user_prompt_modifier(
|
||
input_data: dict[str, Any], tool_use_id: str | None, context: HookContext
|
||
) -> dict[str, Any]:
|
||
"""Add context to user prompts."""
|
||
original_prompt = input_data.get("prompt", "")
|
||
|
||
# Add a timestamp as additional context for Claude to see
|
||
from datetime import datetime
|
||
|
||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
return {
|
||
"hookSpecificOutput": {
|
||
"hookEventName": "UserPromptSubmit",
|
||
"additionalContext": f"[Submitted at {timestamp}] Original prompt: {original_prompt}",
|
||
}
|
||
}
|
||
|
||
async def main():
|
||
options = ClaudeAgentOptions(
|
||
hooks={
|
||
"PreToolUse": [
|
||
HookMatcher(hooks=[pre_tool_logger]),
|
||
HookMatcher(matcher="Bash", hooks=[pre_tool_logger]),
|
||
],
|
||
"PostToolUse": [HookMatcher(hooks=[post_tool_logger])],
|
||
"UserPromptSubmit": [HookMatcher(hooks=[user_prompt_modifier])],
|
||
},
|
||
allowed_tools=["Read", "Write", "Bash"],
|
||
)
|
||
|
||
async with ClaudeSDKClient(options=options) as client:
|
||
await client.query("List files in current directory")
|
||
|
||
async for message in client.receive_response():
|
||
# Hooks will automatically log tool usage
|
||
pass
|
||
|
||
asyncio.run(main())
|
||
```
|
||
|
||
### Real-time Progress Monitoring
|
||
|
||
```python
|
||
from claude_agent_sdk import (
|
||
ClaudeSDKClient,
|
||
ClaudeAgentOptions,
|
||
AssistantMessage,
|
||
ToolUseBlock,
|
||
ToolResultBlock,
|
||
TextBlock,
|
||
)
|
||
import asyncio
|
||
|
||
async def monitor_progress():
|
||
options = ClaudeAgentOptions(
|
||
allowed_tools=["Write", "Bash"], permission_mode="acceptEdits"
|
||
)
|
||
|
||
async with ClaudeSDKClient(options=options) as client:
|
||
await client.query("Create 5 Python files with different sorting algorithms")
|
||
|
||
# Monitor progress in real-time
|
||
async for message in client.receive_response():
|
||
if isinstance(message, AssistantMessage):
|
||
for block in message.content:
|
||
if isinstance(block, ToolUseBlock):
|
||
if block.name == "Write":
|
||
file_path = block.input.get("file_path", "")
|
||
print(f"Creating: {file_path}")
|
||
elif isinstance(block, ToolResultBlock):
|
||
print("Completed tool execution")
|
||
elif isinstance(block, TextBlock):
|
||
print(f"Claude says: {block.text[:100]}...")
|
||
|
||
print("Task completed!")
|
||
|
||
asyncio.run(monitor_progress())
|
||
```
|
||
|
||
## Example Usage
|
||
|
||
### Basic file operations (using query)
|
||
|
||
```python
|
||
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock
|
||
import asyncio
|
||
|
||
async def create_project():
|
||
options = ClaudeAgentOptions(
|
||
allowed_tools=["Read", "Write", "Bash"],
|
||
permission_mode="acceptEdits",
|
||
cwd="/home/user/project",
|
||
)
|
||
|
||
async for message in query(
|
||
prompt="Create a Python project structure with setup.py", options=options
|
||
):
|
||
if isinstance(message, AssistantMessage):
|
||
for block in message.content:
|
||
if isinstance(block, ToolUseBlock):
|
||
print(f"Using tool: {block.name}")
|
||
|
||
asyncio.run(create_project())
|
||
```
|
||
|
||
### Error handling
|
||
|
||
```python
|
||
from claude_agent_sdk import query, CLINotFoundError, ProcessError, CLIJSONDecodeError
|
||
|
||
try:
|
||
async for message in query(prompt="Hello"):
|
||
print(message)
|
||
except CLINotFoundError:
|
||
print(
|
||
"Claude Code CLI not found. Try reinstalling: pip install --force-reinstall claude-agent-sdk"
|
||
)
|
||
except ProcessError as e:
|
||
print(f"Process failed with exit code: {e.exit_code}")
|
||
except CLIJSONDecodeError as e:
|
||
print(f"Failed to parse response: {e}")
|
||
```
|
||
|
||
### Streaming mode with client
|
||
|
||
```python
|
||
from claude_agent_sdk import ClaudeSDKClient
|
||
import asyncio
|
||
|
||
async def interactive_session():
|
||
async with ClaudeSDKClient() as client:
|
||
# Send initial message
|
||
await client.query("What's the weather like?")
|
||
|
||
# Process responses
|
||
async for msg in client.receive_response():
|
||
print(msg)
|
||
|
||
# Send follow-up
|
||
await client.query("Tell me more about that")
|
||
|
||
# Process follow-up response
|
||
async for msg in client.receive_response():
|
||
print(msg)
|
||
|
||
asyncio.run(interactive_session())
|
||
```
|
||
|
||
### Using custom tools with ClaudeSDKClient
|
||
|
||
```python
|
||
from claude_agent_sdk import (
|
||
ClaudeSDKClient,
|
||
ClaudeAgentOptions,
|
||
tool,
|
||
create_sdk_mcp_server,
|
||
AssistantMessage,
|
||
TextBlock,
|
||
)
|
||
import asyncio
|
||
from typing import Any
|
||
|
||
# Define custom tools with @tool decorator
|
||
@tool("calculate", "Perform mathematical calculations", {"expression": str})
|
||
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
|
||
try:
|
||
result = eval(args["expression"], {"__builtins__": {}})
|
||
return {"content": [{"type": "text", "text": f"Result: {result}"}]}
|
||
except Exception as e:
|
||
return {
|
||
"content": [{"type": "text", "text": f"Error: {str(e)}"}],
|
||
"is_error": True,
|
||
}
|
||
|
||
@tool("get_time", "Get current time", {})
|
||
async def get_time(args: dict[str, Any]) -> dict[str, Any]:
|
||
from datetime import datetime
|
||
|
||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
return {"content": [{"type": "text", "text": f"Current time: {current_time}"}]}
|
||
|
||
async def main():
|
||
# Create SDK MCP server with custom tools
|
||
my_server = create_sdk_mcp_server(
|
||
name="utilities", version="1.0.0", tools=[calculate, get_time]
|
||
)
|
||
|
||
# Configure options with the server
|
||
options = ClaudeAgentOptions(
|
||
mcp_servers={"utils": my_server},
|
||
allowed_tools=["mcp__utils__calculate", "mcp__utils__get_time"],
|
||
)
|
||
|
||
# Use ClaudeSDKClient for interactive tool usage
|
||
async with ClaudeSDKClient(options=options) as client:
|
||
await client.query("What's 123 * 456?")
|
||
|
||
# Process calculation response
|
||
async for message in client.receive_response():
|
||
if isinstance(message, AssistantMessage):
|
||
for block in message.content:
|
||
if isinstance(block, TextBlock):
|
||
print(f"Calculation: {block.text}")
|
||
|
||
# Follow up with time query
|
||
await client.query("What time is it now?")
|
||
|
||
async for message in client.receive_response():
|
||
if isinstance(message, AssistantMessage):
|
||
for block in message.content:
|
||
if isinstance(block, TextBlock):
|
||
print(f"Time: {block.text}")
|
||
|
||
asyncio.run(main())
|
||
```
|
||
|
||
## Sandbox Configuration
|
||
|
||
### SandboxSettings
|
||
|
||
Configuration for sandbox behavior. Use this to enable command sandboxing and configure network restrictions programmatically.
|
||
|
||
```python
|
||
class SandboxSettings(TypedDict, total=False):
|
||
enabled: bool
|
||
autoAllowBashIfSandboxed: bool
|
||
excludedCommands: list[str]
|
||
allowUnsandboxedCommands: bool
|
||
network: SandboxNetworkConfig
|
||
ignoreViolations: SandboxIgnoreViolations
|
||
enableWeakerNestedSandbox: bool
|
||
```
|
||
|
||
| Property | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `enabled` | `bool` | `False` | Enable sandbox mode for command execution |
|
||
| `autoAllowBashIfSandboxed` | `bool` | `True` | Auto-approve bash commands when sandbox is enabled |
|
||
| `excludedCommands` | `list[str]` | `[]` | Commands that always bypass sandbox restrictions (e.g., `["docker"]`). These run unsandboxed automatically without model involvement |
|
||
| `allowUnsandboxedCommands` | `bool` | `True` | Allow the model to request running commands outside the sandbox. When `True`, the model can set `dangerouslyDisableSandbox` in tool input, which falls back to the [permissions system](#permissions-fallback-for-unsandboxed-commands) |
|
||
| `network` | [`SandboxNetworkConfig`](#sandbox-network-config) | `None` | Network-specific sandbox configuration |
|
||
| `ignoreViolations` | [`SandboxIgnoreViolations`](#sandbox-ignore-violations) | `None` | Configure which sandbox violations to ignore |
|
||
| `enableWeakerNestedSandbox` | `bool` | `False` | Enable a weaker nested sandbox for compatibility |
|
||
|
||
**Filesystem and network access restrictions** are NOT configured via sandbox settings. Instead, they are derived from [permission rules](https://code.claude.com/docs/en/settings#permission-settings):
|
||
|
||
- **Filesystem read restrictions**: Read deny rules
|
||
- **Filesystem write restrictions**: Edit allow/deny rules
|
||
- **Network restrictions**: WebFetch allow/deny rules
|
||
|
||
Use sandbox settings for command execution sandboxing, and permission rules for filesystem and network access control.
|
||
|
||
#### Example usage
|
||
|
||
```python
|
||
from claude_agent_sdk import query, ClaudeAgentOptions, SandboxSettings
|
||
|
||
sandbox_settings: SandboxSettings = {
|
||
"enabled": True,
|
||
"autoAllowBashIfSandboxed": True,
|
||
"network": {"allowLocalBinding": True},
|
||
}
|
||
|
||
async for message in query(
|
||
prompt="Build and test my project",
|
||
options=ClaudeAgentOptions(sandbox=sandbox_settings),
|
||
):
|
||
print(message)
|
||
```
|
||
|
||
**Unix socket security**: The `allowUnixSockets` option can grant access to powerful system services. For example, allowing `/var/run/docker.sock` effectively grants full host system access through the Docker API, bypassing sandbox isolation. Only allow Unix sockets that are strictly necessary and understand the security implications of each.
|
||
|
||
### SandboxNetworkConfig
|
||
|
||
Network-specific configuration for sandbox mode.
|
||
|
||
```python
|
||
class SandboxNetworkConfig(TypedDict, total=False):
|
||
allowLocalBinding: bool
|
||
allowUnixSockets: list[str]
|
||
allowAllUnixSockets: bool
|
||
httpProxyPort: int
|
||
socksProxyPort: int
|
||
```
|
||
|
||
| Property | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `allowLocalBinding` | `bool` | `False` | Allow processes to bind to local ports (e.g., for dev servers) |
|
||
| `allowUnixSockets` | `list[str]` | `[]` | Unix socket paths that processes can access (e.g., Docker socket) |
|
||
| `allowAllUnixSockets` | `bool` | `False` | Allow access to all Unix sockets |
|
||
| `httpProxyPort` | `int` | `None` | HTTP proxy port for network requests |
|
||
| `socksProxyPort` | `int` | `None` | SOCKS proxy port for network requests |
|
||
|
||
### SandboxIgnoreViolations
|
||
|
||
Configuration for ignoring specific sandbox violations.
|
||
|
||
```python
|
||
class SandboxIgnoreViolations(TypedDict, total=False):
|
||
file: list[str]
|
||
network: list[str]
|
||
```
|
||
|
||
| Property | Type | Default | Description |
|
||
| --- | --- | --- | --- |
|
||
| `file` | `list[str]` | `[]` | File path patterns to ignore violations for |
|
||
| `network` | `list[str]` | `[]` | Network patterns to ignore violations for |
|
||
|
||
### Permissions Fallback for Unsandboxed Commands
|
||
|
||
When `allowUnsandboxedCommands` is enabled, the model can request to run commands outside the sandbox by setting `dangerouslyDisableSandbox: True` in the tool input. These requests fall back to the existing permissions system, meaning your `can_use_tool` handler will be invoked, allowing you to implement custom authorization logic.
|
||
|
||
**`excludedCommands` vs `allowUnsandboxedCommands`:**
|
||
|
||
- `excludedCommands`: A static list of commands that always bypass the sandbox automatically (e.g., `["docker"]`). The model has no control over this.
|
||
- `allowUnsandboxedCommands`: Lets the model decide at runtime whether to request unsandboxed execution by setting `dangerouslyDisableSandbox: True` in the tool input.
|
||
|
||
```python
|
||
from claude_agent_sdk import (
|
||
query,
|
||
ClaudeAgentOptions,
|
||
HookMatcher,
|
||
PermissionResultAllow,
|
||
PermissionResultDeny,
|
||
ToolPermissionContext,
|
||
)
|
||
|
||
async def can_use_tool(
|
||
tool: str, input: dict, context: ToolPermissionContext
|
||
) -> PermissionResultAllow | PermissionResultDeny:
|
||
# Check if the model is requesting to bypass the sandbox
|
||
if tool == "Bash" and input.get("dangerouslyDisableSandbox"):
|
||
# The model is requesting to run this command outside the sandbox
|
||
print(f"Unsandboxed command requested: {input.get('command')}")
|
||
|
||
if is_command_authorized(input.get("command")):
|
||
return PermissionResultAllow()
|
||
return PermissionResultDeny(
|
||
message="Command not authorized for unsandboxed execution"
|
||
)
|
||
return PermissionResultAllow()
|
||
|
||
# Required: dummy hook keeps the stream open for can_use_tool
|
||
async def dummy_hook(input_data, tool_use_id, context):
|
||
return {"continue_": True}
|
||
|
||
async def prompt_stream():
|
||
yield {
|
||
"type": "user",
|
||
"message": {"role": "user", "content": "Deploy my application"},
|
||
}
|
||
|
||
async def main():
|
||
async for message in query(
|
||
prompt=prompt_stream(),
|
||
options=ClaudeAgentOptions(
|
||
sandbox={
|
||
"enabled": True,
|
||
"allowUnsandboxedCommands": True, # Model can request unsandboxed execution
|
||
},
|
||
permission_mode="default",
|
||
can_use_tool=can_use_tool,
|
||
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
|
||
),
|
||
):
|
||
print(message)
|
||
```
|
||
|
||
This pattern enables you to:
|
||
|
||
- **Audit model requests**: Log when the model requests unsandboxed execution
|
||
- **Implement allowlists**: Only permit specific commands to run unsandboxed
|
||
- **Add approval workflows**: Require explicit authorization for privileged operations
|
||
|
||
Commands running with `dangerouslyDisableSandbox: True` have full system access. Ensure your `can_use_tool` handler validates these requests carefully.
|
||
|
||
If `permission_mode` is set to `bypassPermissions` and `allow_unsandboxed_commands` is enabled, the model can autonomously execute commands outside the sandbox without any approval prompts. This combination effectively allows the model to escape sandbox isolation silently. |