diff --git a/maskinrommet/src/agent.rs b/maskinrommet/src/agent.rs index 4b9bdf8..5e3d072 100644 --- a/maskinrommet/src/agent.rs +++ b/maskinrommet/src/agent.rs @@ -155,35 +155,64 @@ Svar KUN med meldingsteksten. {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 project_dir = std::env::var("PROJECT_DIR").unwrap_or_else(|_| "/home/vegard/synops".to_string()); tracing::info!(prompt_len = prompt.len(), "Kaller claude CLI"); - let output = tokio::process::Command::new(&claude_path) - .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 max_retries = 3u32; + let mut response_text = String::new(); - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - return Err(format!("claude feilet ({}): {}", output.status, &stderr[..stderr.len().min(500)])); + for attempt in 0..=max_retries { + let output = tokio::process::Command::new(&claude_path) + .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::(&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::(&stdout) { - Ok(json) => json["result"].as_str().unwrap_or("").to_string(), - Err(_) => stdout.trim().to_string(), - }; - if response_text.is_empty() { return Err("Tom respons fra claude".to_string()); }