claude_code_sdk/
errors.rs

1//! Error types for Claude SDK.
2
3use thiserror::Error;
4use tracing::error;
5use crate::config::SafetyError;
6
7/// Base error type for all Claude SDK errors
8#[derive(Error, Debug)]
9pub enum ClaudeSDKError {
10    #[error("CLI connection error: {0}")]
11    CLIConnection(#[from] CLIConnectionError),
12    
13    #[error("CLI not found: {0}")]
14    CLINotFound(#[from] CLINotFoundError),
15    
16    #[error("Process error: {0}")]
17    Process(#[from] ProcessError),
18    
19    #[error("JSON decode error: {0}")]
20    CLIJSONDecode(#[from] CLIJSONDecodeError),
21    
22    #[error("IO error: {0}")]
23    Io(#[from] std::io::Error),
24    
25    #[error("Safety limit violation: {0}")]
26    Safety(#[from] SafetyError),
27    
28    #[error("Other error: {0}")]
29    Other(String),
30}
31
32/// Raised when unable to connect to Claude Code
33#[derive(Error, Debug)]
34#[error("Unable to connect to Claude Code: {message}")]
35pub struct CLIConnectionError {
36    pub message: String,
37}
38
39impl CLIConnectionError {
40    pub fn new(message: impl Into<String>) -> Self {
41        let message_str = message.into();
42        error!(message = %message_str, "CLI connection error occurred");
43        Self {
44            message: message_str,
45        }
46    }
47}
48
49/// Raised when Claude Code is not found or not installed
50#[derive(Error, Debug)]
51#[error("Claude Code not found: {message}")]
52pub struct CLINotFoundError {
53    pub message: String,
54    pub cli_path: Option<String>,
55}
56
57impl CLINotFoundError {
58    pub fn new(message: impl Into<String>) -> Self {
59        let message_str = message.into();
60        error!(message = %message_str, "Claude CLI not found");
61        Self {
62            message: message_str,
63            cli_path: None,
64        }
65    }
66    
67    pub fn with_path(message: impl Into<String>, cli_path: impl Into<String>) -> Self {
68        let cli_path_string = cli_path.into();
69        let message_str = message.into();
70        error!(
71            message = %message_str,
72            cli_path = %cli_path_string,
73            "Claude CLI not found at specified path"
74        );
75        Self {
76            message: format!("{}: {}", message_str, cli_path_string),
77            cli_path: Some(cli_path_string),
78        }
79    }
80}
81
82/// Raised when the CLI process fails
83#[derive(Error, Debug)]
84pub struct ProcessError {
85    pub message: String,
86    pub exit_code: Option<i32>,
87    pub stderr: Option<String>,
88}
89
90impl ProcessError {
91    pub fn new(message: impl Into<String>) -> Self {
92        let message_str = message.into();
93        error!(message = %message_str, "Process error occurred");
94        Self {
95            message: message_str,
96            exit_code: None,
97            stderr: None,
98        }
99    }
100    
101    pub fn with_exit_code(message: impl Into<String>, exit_code: i32) -> Self {
102        let message_str = message.into();
103        error!(
104            message = %message_str,
105            exit_code = exit_code,
106            "Process error with exit code"
107        );
108        Self {
109            message: message_str,
110            exit_code: Some(exit_code),
111            stderr: None,
112        }
113    }
114    
115    pub fn with_stderr(message: impl Into<String>, exit_code: Option<i32>, stderr: impl Into<String>) -> Self {
116        let message_str = message.into();
117        let stderr_str = stderr.into();
118        error!(
119            message = %message_str,
120            exit_code = exit_code,
121            stderr_preview = %stderr_str.chars().take(200).collect::<String>(),
122            "Process error with stderr output"
123        );
124        Self {
125            message: message_str,
126            exit_code,
127            stderr: Some(stderr_str),
128        }
129    }
130}
131
132impl std::fmt::Display for ProcessError {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        write!(f, "{}", self.message)?;
135        
136        if let Some(exit_code) = self.exit_code {
137            write!(f, " (exit code: {})", exit_code)?;
138        }
139        
140        if let Some(stderr) = &self.stderr {
141            write!(f, "\nError output: {}", stderr)?;
142        }
143        
144        Ok(())
145    }
146}
147
148/// Raised when unable to decode JSON from CLI output
149#[derive(Error, Debug)]
150pub struct CLIJSONDecodeError {
151    pub line: String,
152    pub original_error: serde_json::Error,
153}
154
155impl CLIJSONDecodeError {
156    pub fn new(line: impl Into<String>, original_error: serde_json::Error) -> Self {
157        let line_str = line.into();
158        error!(
159            line_preview = %line_str.chars().take(100).collect::<String>(),
160            parse_error = %original_error,
161            "Failed to decode JSON from CLI output"
162        );
163        Self {
164            line: line_str,
165            original_error,
166        }
167    }
168}
169
170impl std::fmt::Display for CLIJSONDecodeError {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        let line_preview = if self.line.len() > 100 {
173            format!("{}...", &self.line[..100])
174        } else {
175            self.line.clone()
176        };
177        
178        write!(f, "Failed to decode JSON: {}", line_preview)
179    }
180}