1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//! CLI response parsing and diagnostics.
use super::{ClaudeCliResponse, ClaudeCodeProvider};
use tracing::{debug, error, warn};
impl ClaudeCodeProvider {
/// Parse the JSON response from Claude Code CLI, with diagnostic logging.
pub(super) fn parse_response(
&self,
stdout: &str,
effective_max_turns: u32,
) -> (String, Option<String>) {
match serde_json::from_str::<ClaudeCliResponse>(stdout) {
Ok(resp) => {
// Handle error_max_turns — still extract whatever result exists.
if resp.subtype.as_deref() == Some("error_max_turns") {
warn!(
"claude hit max_turns limit | num_turns={:?} effective_limit={} configured={}",
resp.num_turns, effective_max_turns, self.max_turns
);
}
let text = match resp.result {
Some(ref r) if !r.is_empty() => r.clone(),
_ => {
// Log the full response for debugging empty results.
error!(
"claude returned empty result | is_error={} subtype={:?} \
type={:?} num_turns={:?} | raw_len={}",
resp.is_error,
resp.subtype,
resp.response_type,
resp.num_turns,
stdout.len(),
);
debug!("full claude response for empty result: {stdout}");
if resp.is_error {
format!(
"Error from Claude: {}",
resp.subtype.as_deref().unwrap_or("unknown")
)
} else {
// Try to provide something useful instead of a generic fallback.
"I received your message but wasn't able to generate a response. \
Please try again."
.to_string()
}
}
};
(text, resp.model)
}
Err(e) => {
// JSON parsing failed — try to extract text from raw output.
warn!("failed to parse claude JSON response: {e}");
debug!(
"raw stdout (first 500 chars): {}",
&stdout[..stdout.len().min(500)]
);
let trimmed = stdout.trim();
if trimmed.is_empty() {
(
"I received your message but the response was empty. Please try again."
.to_string(),
None,
)
} else {
(trimmed.to_string(), None)
}
}
}
}
}