or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration-options.mdcustom-tools.mderror-handling.mdhook-system.mdindex.mdinteractive-client.mdmessage-types.mdsimple-queries.mdtransport-system.md

interactive-client.mddocs/

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.