{"record":{"id":"1f24b3b238f29655","repo":"Hmbown/CodeWhale","slug":"anthropic-api-error-http-status-error-type","errorCode":null,"errorMessage":"Anthropic API error (HTTP {status} {error_type}): {message}","messagePattern":"Anthropic API error \\(HTTP (.+?) (.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/client/anthropic.rs","lineNumber":237,"sourceCode":"            .send()\n            .await\n            .context(\"Anthropic Messages API request failed\")?;\n        self.check_anthropic_response(response).await\n    }\n\n    /// Shared status/error-envelope handling for streaming and\n    /// non-streaming Messages responses.\n    async fn check_anthropic_response(\n        &self,\n        response: reqwest::Response,\n    ) -> Result<reqwest::Response> {\n        let status = response.status();\n        if !status.is_success() {\n            let raw = bounded_error_text(response, ERROR_BODY_MAX_BYTES).await;\n            let (error_type, message) = parse_anthropic_error_envelope(&raw);\n            self.mark_request_failure(&format!(\"anthropic status={status}\"))\n                .await;\n            anyhow::bail!(\"Anthropic API error (HTTP {status} {error_type}): {message}\");\n        }\n        self.mark_request_success().await;\n        Ok(response)\n    }\n\n    /// Open the streaming Messages request through the shared stream-entry\n    /// transport policy: bounded header wait, dual-client selection, and at\n    /// most one HTTP/1.1 fallback retry on a classified H2 header stall.\n    /// Wire-specific request construction (headers, endpoint, body) stays\n    /// here at the adapter edge.\n    async fn open_anthropic_stream_response(\n        &self,\n        url: &str,\n        body: &Value,\n    ) -> Result<reqwest::Response> {\n        let url = self.messages_transport_url(url);\n        let open_req = super::stream_entry::StreamOpenRequest::new(\n            super::stream_entry::stream_open_timeout(),","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/client/anthropic.rs#L219-L255","documentation":"`check_anthropic_response` is the shared status gate for streaming and non-streaming Anthropic Messages calls: on a non-2xx it reads the body (64KB cap), parses the `{type, message}` error envelope via `parse_anthropic_error_envelope`, records the failure for the request-health/circuit-breaker logic (`mark_request_failure`), and bails with `HTTP {status} {error_type}: {message}`. When the body is not an Anthropic envelope, the type/message fall back to generic values.","triggerScenarios":"401 `authentication_error` (bad `x-api-key`/authorization header); 400 `invalid_request_error` (malformed tools, bad `max_tokens`, unsupported parameter for the model); 404 model not found; 413 oversize prompt; 429 `rate_limit_error`; 529 `overloaded_error`; 5xx `api_error`. Each also feeds failure-streak tracking, so repeated failures can trip the client's failure marking.","commonSituations":"Expired or copied-with-whitespace API key; requesting a retired model id after Anthropic deprecations; hitting org rate limits during bulk sessions; anthropic-compatible proxies (Bedrock/frontier gateways) returning their own envelope shapes; long prompts exceeding context limits surfaced as 400.","solutions":["Match the `error_type` in the message: `authentication_error` → fix the API key; `invalid_request_error` → fix the named parameter/model; `rate_limit_error` → back off and retry later; `overloaded_error`/5xx → retry with backoff.","Verify the model id still exists on the Anthropic model catalog.","For 429s, reduce request frequency or concurrency; for context-size 400s, trim or compact the conversation.","If using a proxy, compare its error envelope with the official Anthropic shape — mismatched envelopes degrade this message but the status still tells you the class."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"fn is_retryable_anthropic_status(status: u16) -> bool {\n    matches!(status, 408 | 429 | 500 | 502 | 503 | 529 | 524)\n}","tryCatchPattern":"let mut attempt = 0;\nloop {\n    attempt += 1;\n    match client.create_message(req.clone()).await {\n        Ok(resp) => break Ok(resp),\n        Err(e) => {\n            let msg = e.to_string();\n            if msg.contains(\"HTTP 429\") || msg.contains(\"529\") || msg.contains(\"HTTP 5\") {\n                if attempt >= MAX_RETRIES { break Err(e); }\n                tokio::time::sleep(backoff(attempt)).await; // exponential + jitter\n                continue;\n            }\n            if msg.contains(\"HTTP 401\") || msg.contains(\"authentication_error\") { fix_key_and_stop(); }\n            break Err(e); // 400 invalid_request: fix the request, do not retry\n        }\n    }\n}","preventionTips":["Never retry 400 invalid_request_error — read the named parameter and fix the request payload.","Honor 429 rate-limit windows from the error message; use exponential backoff with jitter for 429/529/5xx.","Track model deprecations and update model ids before they 404.","Keep prompts under context limits by compacting conversations proactively."],"tags":["anthropic","http","api","authentication","rate-limit"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}