llm: catch empty and rate-limited responses as errors

Empty stdout and Claude's rate limit message were silently returned
as successful 0-byte responses. Now detected and reported as errors.

Also skip transcript segments with fewer than 2 assistant messages
(rate-limited sessions, stub conversations).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kent Overstreet 2026-03-16 19:28:13 -04:00
parent 7fe55e28bd
commit d7436b8b9c
2 changed files with 21 additions and 0 deletions

View file

@ -754,6 +754,19 @@ pub fn select_conversation_fragments(n: usize) -> Vec<(String, String)> {
if store.is_segment_mined(&session_id, seg_idx as u32, "observation") {
continue;
}
// Skip segments with too few assistant messages (rate limits, errors)
let assistant_msgs = segment.iter()
.filter(|(_, role, _, _)| role == "assistant")
.count();
if assistant_msgs < 2 {
continue;
}
// Skip segments that are just rate limit errors
let has_rate_limit = segment.iter().any(|(_, _, text, _)|
text.contains("hit your limit") || text.contains("rate limit"));
if has_rate_limit && assistant_msgs < 3 {
continue;
}
let text = format_segment(&segment, 8000);
if text.len() > 500 {
let id = format!("{}.{}", session_id, seg_idx);

View file

@ -152,6 +152,14 @@ fn call_model_with_tools(agent: &str, model: &str, prompt: &str,
}
if output.status.success() {
let response = String::from_utf8_lossy(&output.stdout).trim().to_string();
if response.is_empty() {
log_usage(agent, model, prompt, "EMPTY", elapsed, false);
return Err("claude returned empty response".into());
}
if response.contains(": You've hit your limit \u{00b7} resets") {
log_usage(agent, model, prompt, "RATE_LIMITED", elapsed, false);
return Err(format!("rate limited: {}", crate::util::first_n_chars(&response, 200)));
}
log_usage(agent, model, prompt, &response, elapsed, true);
Ok(response)
} else {