0
# Interactive Client
1
2
The `ClaudeSDKClient` provides bidirectional, interactive conversations with Claude Code. This client offers full control over the conversation flow with support for streaming, interrupts, dynamic message sending, custom tools, and hooks.
3
4
## Capabilities
5
6
### Client Class
7
8
Main client class for interactive conversations with full control over message flow and advanced features.
9
10
```python { .api }
11
class ClaudeSDKClient:
12
"""
13
Client for bidirectional, interactive conversations with Claude Code.
14
15
Key features:
16
- Bidirectional: Send and receive messages at any time
17
- Stateful: Maintains conversation context across messages
18
- Interactive: Send follow-ups based on responses
19
- Control flow: Support for interrupts and session management
20
"""
21
22
def __init__(self, options: ClaudeCodeOptions | None = None):
23
"""
24
Initialize Claude SDK client.
25
26
Args:
27
options: Optional configuration (defaults to ClaudeCodeOptions() if None)
28
"""
29
```
30
31
### Connection Management
32
33
Establish and manage connections to Claude Code with optional initial prompts.
34
35
```python { .api }
36
async def connect(
37
self, prompt: str | AsyncIterable[dict[str, Any]] | None = None
38
) -> None:
39
"""
40
Connect to Claude with a prompt or message stream.
41
42
Args:
43
prompt: Optional initial prompt. Can be string, async iterable of messages, or None
44
for empty connection that stays open for interactive use
45
"""
46
47
async def disconnect(self) -> None:
48
"""Disconnect from Claude."""
49
```
50
51
### Message Communication
52
53
Send and receive messages with full bidirectional control.
54
55
```python { .api }
56
async def receive_messages(self) -> AsyncIterator[Message]:
57
"""
58
Receive all messages from Claude.
59
60
Yields:
61
Message: All messages in the conversation stream
62
"""
63
64
async def query(
65
self, prompt: str | AsyncIterable[dict[str, Any]], session_id: str = "default"
66
) -> None:
67
"""
68
Send a new request in streaming mode.
69
70
Args:
71
prompt: Either a string message or an async iterable of message dictionaries
72
session_id: Session identifier for the conversation
73
"""
74
75
async def receive_response(self) -> AsyncIterator[Message]:
76
"""
77
Receive messages from Claude until and including a ResultMessage.
78
79
This async iterator yields all messages in sequence and automatically terminates
80
after yielding a ResultMessage (which indicates the response is complete).
81
82
Yields:
83
Message: Each message received (UserMessage, AssistantMessage, SystemMessage, ResultMessage)
84
"""
85
```
86
87
### Control Operations
88
89
Interrupt conversations and retrieve server information.
90
91
```python { .api }
92
async def interrupt(self) -> None:
93
"""Send interrupt signal (only works with streaming mode)."""
94
95
async def get_server_info(self) -> dict[str, Any] | None:
96
"""
97
Get server initialization info including available commands and output styles.
98
99
Returns:
100
Dictionary with server info, or None if not in streaming mode
101
"""
102
```
103
104
### Context Management
105
106
Support for async context manager pattern.
107
108
```python { .api }
109
async def __aenter__(self) -> "ClaudeSDKClient":
110
"""Enter async context - automatically connects with empty stream for interactive use."""
111
112
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> bool:
113
"""Exit async context - always disconnects."""
114
```
115
116
## Usage Examples
117
118
### Basic Interactive Session
119
120
```python
121
from claude_code_sdk import ClaudeSDKClient, AssistantMessage, TextBlock
122
123
async def main():
124
async with ClaudeSDKClient() as client:
125
# Send initial query
126
await client.query("Hello, how are you?")
127
128
# Receive and process response
129
async for msg in client.receive_response():
130
if isinstance(msg, AssistantMessage):
131
for block in msg.content:
132
if isinstance(block, TextBlock):
133
print(f"Claude: {block.text}")
134
135
# Send follow-up based on response
136
await client.query("Can you help me write a Python function?")
137
138
async for msg in client.receive_response():
139
if isinstance(msg, AssistantMessage):
140
for block in msg.content:
141
if isinstance(block, TextBlock):
142
print(f"Claude: {block.text}")
143
144
anyio.run(main)
145
```
146
147
### Configuration with Options
148
149
```python
150
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
151
152
async def main():
153
options = ClaudeCodeOptions(
154
allowed_tools=["Read", "Write", "Bash"],
155
permission_mode="acceptEdits",
156
system_prompt="You are a helpful coding assistant",
157
cwd="/path/to/project"
158
)
159
160
async with ClaudeSDKClient(options=options) as client:
161
await client.query("Create a Python web server")
162
163
async for msg in client.receive_response():
164
print(msg)
165
166
anyio.run(main)
167
```
168
169
### Custom Tools Integration
170
171
```python
172
from claude_code_sdk import (
173
ClaudeSDKClient, ClaudeCodeOptions,
174
tool, create_sdk_mcp_server
175
)
176
177
@tool("greet", "Greet a user", {"name": str})
178
async def greet_user(args):
179
return {
180
"content": [
181
{"type": "text", "text": f"Hello, {args['name']}!"}
182
]
183
}
184
185
async def main():
186
# Create SDK MCP server
187
server = create_sdk_mcp_server(
188
name="my-tools",
189
version="1.0.0",
190
tools=[greet_user]
191
)
192
193
options = ClaudeCodeOptions(
194
mcp_servers={"tools": server},
195
allowed_tools=["mcp__tools__greet"]
196
)
197
198
async with ClaudeSDKClient(options=options) as client:
199
await client.query("Greet Alice")
200
201
async for msg in client.receive_response():
202
print(msg)
203
204
anyio.run(main)
205
```
206
207
### Hook System Integration
208
209
```python
210
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions, HookMatcher
211
212
async def check_bash_command(input_data, tool_use_id, context):
213
tool_name = input_data["tool_name"]
214
tool_input = input_data["tool_input"]
215
216
if tool_name != "Bash":
217
return {}
218
219
command = tool_input.get("command", "")
220
forbidden_patterns = ["rm -rf", "format"]
221
222
for pattern in forbidden_patterns:
223
if pattern in command:
224
return {
225
"hookSpecificOutput": {
226
"hookEventName": "PreToolUse",
227
"permissionDecision": "deny",
228
"permissionDecisionReason": f"Command contains dangerous pattern: {pattern}",
229
}
230
}
231
return {}
232
233
async def main():
234
options = ClaudeCodeOptions(
235
allowed_tools=["Bash"],
236
hooks={
237
"PreToolUse": [
238
HookMatcher(matcher="Bash", hooks=[check_bash_command]),
239
],
240
}
241
)
242
243
async with ClaudeSDKClient(options=options) as client:
244
await client.query("Run the bash command: echo 'Hello World!'")
245
246
async for msg in client.receive_response():
247
print(msg)
248
249
anyio.run(main)
250
```
251
252
### Interrupt Capability
253
254
```python
255
import asyncio
256
from claude_code_sdk import ClaudeSDKClient
257
258
async def main():
259
async with ClaudeSDKClient() as client:
260
await client.query("Write a very long story about space exploration")
261
262
# Start receiving messages in background
263
async def receive_messages():
264
async for msg in client.receive_messages():
265
print(msg)
266
267
receive_task = asyncio.create_task(receive_messages())
268
269
# Wait a bit, then interrupt
270
await asyncio.sleep(5)
271
await client.interrupt()
272
273
await receive_task
274
275
anyio.run(main)
276
```
277
278
### Server Information
279
280
```python
281
from claude_code_sdk import ClaudeSDKClient
282
283
async def main():
284
async with ClaudeSDKClient() as client:
285
info = await client.get_server_info()
286
287
if info:
288
print(f"Commands available: {len(info.get('commands', []))}")
289
print(f"Output style: {info.get('output_style', 'default')}")
290
291
anyio.run(main)
292
```
293
294
### Manual Connection Management
295
296
```python
297
from claude_code_sdk import ClaudeSDKClient
298
299
async def main():
300
client = ClaudeSDKClient()
301
302
try:
303
await client.connect("Hello Claude")
304
305
async for msg in client.receive_messages():
306
print(msg)
307
# Break after first complete response
308
if hasattr(msg, 'subtype') and msg.subtype == "result":
309
break
310
311
finally:
312
await client.disconnect()
313
314
anyio.run(main)
315
```
316
317
## When to Use ClaudeSDKClient
318
319
**Ideal for:**
320
- Building chat interfaces or conversational UIs
321
- Interactive debugging or exploration sessions
322
- Multi-turn conversations with context
323
- When you need to react to Claude's responses
324
- Real-time applications with user input
325
- When you need interrupt capabilities
326
- Using custom tools and hooks
327
- Applications requiring advanced MCP features
328
329
**Key Advantages over query():**
330
- Bidirectional communication
331
- Stateful conversations
332
- Interrupt support
333
- Custom tool integration
334
- Hook system support
335
- Server information access
336
- Fine-grained control over message flow
337
338
## Important Limitations
339
340
**Runtime Context**: As of v0.0.20, you cannot use a ClaudeSDKClient instance across different async runtime contexts (e.g., different trio nurseries or asyncio task groups). The client maintains a persistent anyio task group that remains active from connect() until disconnect(), so all operations must be completed within the same async context where it was connected.
341
342
## Error Handling
343
344
All ClaudeSDKClient methods can raise various exceptions:
345
346
- `CLIConnectionError`: When not connected or connection issues occur
347
- `CLINotFoundError`: When Claude Code is not installed
348
- `ProcessError`: When the underlying CLI process fails
349
- `CLIJSONDecodeError`: When response parsing fails
350
351
See [Error Handling](./error-handling.md) for complete error handling information.