{"record":{"id":"44f5a4ca664bbeb0","repo":"gitbutlerapp/gitbutler","slug":"anthropic-api-error-status-error-text","errorCode":null,"errorMessage":"Anthropic API error ({status}): {error_text}","messagePattern":"Anthropic API error \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-llm/src/anthropic.rs","lineNumber":83,"sourceCode":"    async fn message(&self, request: &AnthropicRequest) -> Result<AnthropicResponse> {\n        let response = self.message_raw(request).await?;\n        let anthropic_response: AnthropicResponse = response.json().await?;\n        Ok(anthropic_response)\n    }\n\n    async fn message_raw(&self, request: &AnthropicRequest) -> Result<Response> {\n        let api_base = self.api_base();\n        let response = self\n            .client\n            .post(format!(\"{api_base}/messages\"))\n            .json(request)\n            .send()\n            .await?;\n\n        if !response.status().is_success() {\n            let status = response.status();\n            let error_text = response.text().await?;\n            anyhow::bail!(\"Anthropic API error ({status}): {error_text}\");\n        }\n\n        Ok(response)\n    }\n\n    fn api_base(&self) -> &str {\n        match self.kind {\n            CredentialsKind::GitButlerProxied => GB_ANTHROPIC_API_BASE,\n            _ => ANTHROPIC_API_BASE,\n        }\n    }\n}\n\n#[derive(Debug, Clone)]\npub struct AnthropicProvider {\n    model: Option<String>,\n    credentials: (CredentialsKind, Sensitive<String>),\n}","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-llm/src/anthropic.rs#L65-L101","documentation":"This error is raised in message_raw when the Anthropic HTTP API returns a non-2xx status. The library bails with the HTTP status code and the full response body so the developer can see exactly what the API rejected (auth failure, bad model name, rate limit, malformed request, etc.). It wraps every possible server-side rejection into one anyhow error.","triggerScenarios":"Calling message, stream_response, or tool_calling_stream against the Anthropic API and receiving any non-success HTTP status (401 invalid API key, 400 bad request/model, 429 rate limit, 5xx server error).","commonSituations":"Expired or missing ANTHROPIC_API_KEY, wrong model identifier, oversized prompt exceeding context window, hitting rate limits, or Anthropic outages.","solutions":["Read the error_text in the message — the Anthropic response body names the exact problem","Verify the API key is set and valid","Check the model name matches an Anthropic model identifier","Inspect request size against the model's context limit; retry with backoff on 429/5xx"],"exampleFix":"// before: blind retry on any failure\nlet msg = provider.message(req).await?;\n// after: inspect status first\nmatch provider.message(req).await {\n    Err(e) if e.to_string().contains(\"429\") => retry_with_backoff(req).await,\n    other => other,\n}","handlingStrategy":"try-catch","validationCode":"// TS/JS\nif (!process.env.ANTHROPIC_API_KEY) throw new Error('ANTHROPIC_API_KEY not set');","typeGuard":null,"tryCatchPattern":"// Rust\nmatch provider.message(req).await {\n    Err(e) => {\n        let msg = e.to_string();\n        if msg.contains(\"429\") { /* backoff + retry */ }\n        else if msg.contains(\"401\") { /* fix API key */ }\n        else { return Err(e); }\n    }\n    Ok(r) => r,\n}","preventionTips":["Validate the API key is present and well-formed before calls","Pin and verify model identifiers","Add retry with exponential backoff for 429/5xx","Monitor Anthropic status for outages"],"tags":["http","api","network","anthropic"],"backgroundTag":"http-error-response","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}