0
# Error Handling
1
2
Comprehensive error handling with specific exception types for different failure modes. The Claude Code SDK provides detailed exception hierarchy for connection errors, process failures, JSON parsing issues, and message parsing errors.
3
4
## Capabilities
5
6
### Base Exception Class
7
8
Base exception for all Claude SDK errors.
9
10
```python { .api }
11
class ClaudeSDKError(Exception):
12
"""Base exception for all Claude SDK errors."""
13
```
14
15
### Connection Errors
16
17
Errors related to connecting to Claude Code CLI.
18
19
```python { .api }
20
class CLIConnectionError(ClaudeSDKError):
21
"""Raised when unable to connect to Claude Code."""
22
23
class CLINotFoundError(CLIConnectionError):
24
"""Raised when Claude Code is not found or not installed."""
25
26
def __init__(
27
self, message: str = "Claude Code not found", cli_path: str | None = None
28
):
29
"""
30
Initialize CLINotFoundError.
31
32
Args:
33
message: Error message
34
cli_path: Path where Claude Code was expected (if known)
35
"""
36
```
37
38
### Process Errors
39
40
Errors related to CLI process execution and failures.
41
42
```python { .api }
43
class ProcessError(ClaudeSDKError):
44
"""Raised when the CLI process fails."""
45
46
def __init__(
47
self, message: str, exit_code: int | None = None, stderr: str | None = None
48
):
49
"""
50
Initialize ProcessError.
51
52
Args:
53
message: Error description
54
exit_code: Process exit code (if available)
55
stderr: Standard error output (if available)
56
"""
57
58
exit_code: int | None
59
stderr: str | None
60
```
61
62
### JSON and Message Parsing Errors
63
64
Errors related to parsing Claude Code CLI output and responses.
65
66
```python { .api }
67
class CLIJSONDecodeError(ClaudeSDKError):
68
"""Raised when unable to decode JSON from CLI output."""
69
70
def __init__(self, line: str, original_error: Exception):
71
"""
72
Initialize CLIJSONDecodeError.
73
74
Args:
75
line: The line that failed to parse
76
original_error: The original JSON decode exception
77
"""
78
79
line: str
80
original_error: Exception
81
```
82
83
## Usage Examples
84
85
### Basic Error Handling
86
87
```python
88
from claude_code_sdk import (
89
query, ClaudeSDKError, CLINotFoundError,
90
CLIConnectionError, ProcessError, CLIJSONDecodeError
91
)
92
93
async def main():
94
try:
95
async for message in query(prompt="Hello Claude"):
96
print(message)
97
98
except CLINotFoundError:
99
print("Error: Claude Code is not installed.")
100
print("Please install with: npm install -g @anthropic-ai/claude-code")
101
102
except CLIConnectionError as e:
103
print(f"Error: Unable to connect to Claude Code: {e}")
104
105
except ProcessError as e:
106
print(f"Error: Claude Code process failed: {e}")
107
if e.exit_code:
108
print(f"Exit code: {e.exit_code}")
109
if e.stderr:
110
print(f"Error output: {e.stderr}")
111
112
except CLIJSONDecodeError as e:
113
print(f"Error: Failed to parse Claude Code response")
114
print(f"Problematic line: {e.line[:100]}")
115
print(f"Original error: {e.original_error}")
116
117
except ClaudeSDKError as e:
118
print(f"Error: General Claude SDK error: {e}")
119
120
except Exception as e:
121
print(f"Unexpected error: {e}")
122
```
123
124
### Specific Error Handling with Recovery
125
126
```python
127
import asyncio
128
import time
129
from claude_code_sdk import (
130
query, ClaudeCodeOptions, CLINotFoundError,
131
CLIConnectionError, ProcessError
132
)
133
134
async def robust_query(prompt: str, max_retries: int = 3) -> list:
135
"""Query with retry logic and error handling."""
136
messages = []
137
last_error = None
138
139
for attempt in range(max_retries):
140
try:
141
async for message in query(prompt=prompt):
142
messages.append(message)
143
return messages
144
145
except CLINotFoundError as e:
146
print("Claude Code not found. Please install it first.")
147
raise e # Don't retry installation errors
148
149
except CLIConnectionError as e:
150
last_error = e
151
print(f"Connection error (attempt {attempt + 1}/{max_retries}): {e}")
152
if attempt < max_retries - 1:
153
await asyncio.sleep(2 ** attempt) # Exponential backoff
154
continue
155
156
except ProcessError as e:
157
last_error = e
158
# Retry on certain exit codes
159
if e.exit_code in [1, 2]: # Retryable errors
160
print(f"Process error (attempt {attempt + 1}/{max_retries}): {e}")
161
if attempt < max_retries - 1:
162
await asyncio.sleep(1)
163
continue
164
else:
165
# Non-retryable process error
166
raise e
167
168
# If we get here, all retries failed
169
raise last_error or Exception("All retry attempts failed")
170
171
async def main():
172
try:
173
messages = await robust_query("What is the capital of France?")
174
for message in messages:
175
print(message)
176
except Exception as e:
177
print(f"Failed after all retries: {e}")
178
```
179
180
### Client Error Handling
181
182
```python
183
from claude_code_sdk import (
184
ClaudeSDKClient, ClaudeCodeOptions,
185
CLIConnectionError, ProcessError
186
)
187
188
async def main():
189
client = ClaudeSDKClient()
190
191
try:
192
await client.connect("Hello Claude")
193
194
async for msg in client.receive_response():
195
print(msg)
196
197
except CLIConnectionError as e:
198
print(f"Failed to connect: {e}")
199
print("Ensure Claude Code is installed and accessible")
200
201
except ProcessError as e:
202
print(f"Process error during conversation: {e}")
203
if e.exit_code == 130: # SIGINT
204
print("Conversation was interrupted")
205
elif e.stderr and "permission" in e.stderr.lower():
206
print("Permission denied - check tool permissions")
207
208
finally:
209
try:
210
await client.disconnect()
211
except Exception as disconnect_error:
212
print(f"Error during disconnect: {disconnect_error}")
213
```
214
215
### Custom Tool Error Handling
216
217
```python
218
from claude_code_sdk import (
219
tool, create_sdk_mcp_server, ClaudeSDKClient,
220
ClaudeCodeOptions, ProcessError
221
)
222
223
@tool("divide", "Divide two numbers", {"a": float, "b": float})
224
async def divide_numbers(args):
225
"""Tool with proper error handling."""
226
try:
227
if args["b"] == 0:
228
return {
229
"content": [
230
{"type": "text", "text": "Error: Division by zero is not allowed"}
231
],
232
"is_error": True
233
}
234
235
result = args["a"] / args["b"]
236
return {
237
"content": [
238
{"type": "text", "text": f"Result: {result}"}
239
]
240
}
241
242
except Exception as e:
243
return {
244
"content": [
245
{"type": "text", "text": f"Unexpected error: {str(e)}"}
246
],
247
"is_error": True
248
}
249
250
async def main():
251
server = create_sdk_mcp_server("calculator", tools=[divide_numbers])
252
options = ClaudeCodeOptions(
253
mcp_servers={"calc": server},
254
allowed_tools=["mcp__calc__divide"]
255
)
256
257
try:
258
async with ClaudeSDKClient(options=options) as client:
259
await client.query("Divide 10 by 0")
260
261
async for msg in client.receive_response():
262
# Check for tool errors in messages
263
if hasattr(msg, 'content'):
264
for block in getattr(msg, 'content', []):
265
if hasattr(block, 'is_error') and block.is_error:
266
print(f"Tool reported error: {block.content}")
267
elif hasattr(block, 'text'):
268
print(f"Response: {block.text}")
269
270
except ProcessError as e:
271
print(f"Process error with custom tools: {e}")
272
```
273
274
### Logging and Debugging
275
276
```python
277
import logging
278
import sys
279
from claude_code_sdk import (
280
query, ClaudeCodeOptions, CLIJSONDecodeError,
281
MessageParseError
282
)
283
284
# Configure logging
285
logging.basicConfig(
286
level=logging.DEBUG,
287
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
288
handlers=[
289
logging.FileHandler('claude_sdk.log'),
290
logging.StreamHandler(sys.stdout)
291
]
292
)
293
294
logger = logging.getLogger('claude_sdk_app')
295
296
async def main():
297
# Enable debug output
298
debug_file = open("claude_debug.log", "w")
299
300
options = ClaudeCodeOptions(
301
debug_stderr=debug_file,
302
allowed_tools=["Read", "Write", "Bash"]
303
)
304
305
try:
306
async for message in query(
307
prompt="Create a complex project structure",
308
options=options
309
):
310
logger.info(f"Received message: {type(message).__name__}")
311
print(message)
312
313
except CLIJSONDecodeError as e:
314
logger.error(f"JSON decode error: {e.line[:100]}")
315
logger.error(f"Original error: {e.original_error}")
316
317
# Log the problematic line for debugging
318
with open("failed_json.log", "w") as f:
319
f.write(f"Failed line: {e.line}\n")
320
f.write(f"Error: {e.original_error}\n")
321
322
except MessageParseError as e:
323
logger.error(f"Message parse error: {e}")
324
if e.data:
325
logger.error(f"Problematic data: {e.data}")
326
327
except Exception as e:
328
logger.exception(f"Unexpected error: {e}")
329
330
finally:
331
debug_file.close()
332
```
333
334
### Context Manager Error Handling
335
336
```python
337
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
338
from contextlib import asynccontextmanager
339
340
@asynccontextmanager
341
async def claude_client_with_recovery(options=None):
342
"""Context manager with automatic error recovery."""
343
client = ClaudeSDKClient(options or ClaudeCodeOptions())
344
345
try:
346
await client.connect()
347
yield client
348
349
except CLIConnectionError as e:
350
print(f"Connection error: {e}")
351
# Attempt recovery
352
try:
353
await client.connect()
354
yield client
355
except Exception as recovery_error:
356
print(f"Recovery failed: {recovery_error}")
357
raise e
358
359
except Exception as e:
360
print(f"Unexpected error in context manager: {e}")
361
raise
362
363
finally:
364
try:
365
await client.disconnect()
366
except Exception as disconnect_error:
367
print(f"Error during cleanup: {disconnect_error}")
368
369
async def main():
370
try:
371
async with claude_client_with_recovery() as client:
372
await client.query("Test with error recovery")
373
374
async for msg in client.receive_response():
375
print(msg)
376
377
except Exception as e:
378
print(f"Final error: {e}")
379
```
380
381
### Validation Error Handling
382
383
```python
384
from claude_code_sdk import ClaudeCodeOptions, ClaudeSDKClient
385
386
async def validate_options(options: ClaudeCodeOptions) -> ClaudeCodeOptions:
387
"""Validate and fix common configuration errors."""
388
# Fix common tool name issues
389
if options.allowed_tools:
390
fixed_tools = []
391
for tool in options.allowed_tools:
392
if tool in ["bash", "Bash"]:
393
fixed_tools.append("Bash")
394
elif tool in ["read", "Read"]:
395
fixed_tools.append("Read")
396
elif tool in ["write", "Write"]:
397
fixed_tools.append("Write")
398
else:
399
fixed_tools.append(tool)
400
401
options.allowed_tools = fixed_tools
402
403
# Check for conflicting permission settings
404
if options.can_use_tool and options.permission_prompt_tool_name:
405
print("Warning: can_use_tool and permission_prompt_tool_name are mutually exclusive")
406
print("Setting permission_prompt_tool_name to None")
407
options.permission_prompt_tool_name = None
408
409
return options
410
411
async def main():
412
try:
413
# Potentially problematic configuration
414
options = ClaudeCodeOptions(
415
allowed_tools=["bash", "read", "write"], # Wrong case
416
can_use_tool=lambda *args: None, # Invalid callback
417
permission_prompt_tool_name="custom" # Conflicting setting
418
)
419
420
# Validate and fix
421
options = await validate_options(options)
422
423
async with ClaudeSDKClient(options=options) as client:
424
await client.query("Test with validated options")
425
426
async for msg in client.receive_response():
427
print(msg)
428
429
except ValueError as e:
430
print(f"Configuration error: {e}")
431
except Exception as e:
432
print(f"Unexpected error: {e}")
433
```
434
435
## Error Categories and Recovery Strategies
436
437
### Installation Errors
438
439
- **Error**: `CLINotFoundError`
440
- **Cause**: Claude Code not installed
441
- **Recovery**: Install Claude Code, check PATH
442
- **Strategy**: Don't retry, show installation instructions
443
444
### Connection Errors
445
446
- **Error**: `CLIConnectionError`
447
- **Cause**: Can't connect to Claude Code process
448
- **Recovery**: Retry with backoff, check process permissions
449
- **Strategy**: Limited retries with exponential backoff
450
451
### Process Errors
452
453
- **Error**: `ProcessError`
454
- **Cause**: Claude Code CLI process failure
455
- **Recovery**: Depends on exit code
456
- **Strategy**: Retry for transient errors, fail for permanent errors
457
458
### Parsing Errors
459
460
- **Error**: `CLIJSONDecodeError`, `MessageParseError`
461
- **Cause**: Malformed output from Claude Code
462
- **Recovery**: Log problematic data, potentially retry
463
- **Strategy**: Log for debugging, limited retries
464
465
## Best Practices
466
467
### Error Handling Guidelines
468
469
1. **Catch specific exceptions** rather than generic `Exception`
470
2. **Provide user-friendly error messages** for common issues
471
3. **Log detailed error information** for debugging
472
4. **Implement appropriate retry logic** for transient errors
473
5. **Clean up resources** in finally blocks or context managers
474
475
### Debugging Support
476
477
1. **Enable debug output** with `debug_stderr` option
478
2. **Log all communication** for troubleshooting
479
3. **Preserve failed data** for error analysis
480
4. **Use structured logging** with appropriate levels
481
482
### Resource Management
483
484
1. **Always disconnect clients** in finally blocks
485
2. **Use context managers** for automatic cleanup
486
3. **Handle disconnect errors** gracefully
487
4. **Monitor for resource leaks** in long-running applications
488
489
For related configuration options, see [Configuration and Options](./configuration-options.md).
490
For transport-specific errors, see [Transport System](./transport-system.md).