Legg til retry med backoff for Claude API-feil (500/529)

Maskinrommet prøver nå opptil 3 ganger med eksponentiell backoff
(2, 4, 8 sek) ved 500/529-feil fra Anthropic. Etter alle forsøk
vises en vennlig melding i chatten i stedet for rå feilmeldinger.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-17 21:56:54 +00:00
parent d4715831bf
commit 244da69110

View file

@ -155,35 +155,64 @@ Svar KUN med meldingsteksten.
{conversation}--- Svar ---"# {conversation}--- Svar ---"#
); );
// Kall claude CLI direkte // Kall claude CLI med retry ved API-feil (500/529)
let claude_path = std::env::var("CLAUDE_PATH").unwrap_or_else(|_| "claude".to_string()); let claude_path = std::env::var("CLAUDE_PATH").unwrap_or_else(|_| "claude".to_string());
let project_dir = std::env::var("PROJECT_DIR").unwrap_or_else(|_| "/home/vegard/synops".to_string()); let project_dir = std::env::var("PROJECT_DIR").unwrap_or_else(|_| "/home/vegard/synops".to_string());
tracing::info!(prompt_len = prompt.len(), "Kaller claude CLI"); tracing::info!(prompt_len = prompt.len(), "Kaller claude CLI");
let output = tokio::process::Command::new(&claude_path) let max_retries = 3u32;
.arg("-p") let mut response_text = String::new();
.arg(&prompt)
.arg("--output-format")
.arg("json")
.arg("--dangerously-skip-permissions")
.env("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1")
.current_dir(&project_dir)
.output()
.await
.map_err(|e| format!("Kunne ikke starte claude: {e}"))?;
if !output.status.success() { for attempt in 0..=max_retries {
let stderr = String::from_utf8_lossy(&output.stderr); let output = tokio::process::Command::new(&claude_path)
return Err(format!("claude feilet ({}): {}", output.status, &stderr[..stderr.len().min(500)])); .arg("-p")
.arg(&prompt)
.arg("--output-format")
.arg("json")
.arg("--dangerously-skip-permissions")
.env("CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC", "1")
.current_dir(&project_dir)
.output()
.await
.map_err(|e| format!("Kunne ikke starte claude: {e}"))?;
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
// Sjekk om dette er en retrybar API-feil (500/529)
let is_api_error = !output.status.success()
&& (stderr.contains("500") || stderr.contains("529")
|| stderr.contains("overloaded") || stderr.contains("Internal Server Error"));
if is_api_error && attempt < max_retries {
let delay = std::time::Duration::from_secs(2u64.pow(attempt + 1)); // 2, 4, 8 sek
tracing::warn!(
attempt = attempt + 1,
delay_secs = delay.as_secs(),
"Claude API-feil, prøver igjen"
);
tokio::time::sleep(delay).await;
continue;
}
if !output.status.success() {
if is_api_error {
// Alle retries brukt opp — gi vennlig melding i stedet for rå feil
tracing::error!(attempts = max_retries + 1, "Claude API utilgjengelig etter alle forsøk");
response_text = "Beklager, jeg er midlertidig utilgjengelig — Anthropic sitt API svarer ikke akkurat nå. Prøv igjen om litt.".to_string();
break;
}
return Err(format!("claude feilet ({}): {}", output.status, &stderr[..stderr.len().min(500)]));
}
response_text = match serde_json::from_str::<serde_json::Value>(&stdout) {
Ok(json) => json["result"].as_str().unwrap_or("").to_string(),
Err(_) => stdout.trim().to_string(),
};
break;
} }
let stdout = String::from_utf8_lossy(&output.stdout);
let response_text = match serde_json::from_str::<serde_json::Value>(&stdout) {
Ok(json) => json["result"].as_str().unwrap_or("").to_string(),
Err(_) => stdout.trim().to_string(),
};
if response_text.is_empty() { if response_text.is_empty() {
return Err("Tom respons fra claude".to_string()); return Err("Tom respons fra claude".to_string());
} }