{"record":{"id":"1673f64aef97c490","repo":"zeroclaw-labs/zeroclaw","slug":"api-error-status-sanitized","errorCode":null,"errorMessage":"{} API error ({status}): {sanitized}","messagePattern":"(.+?) API error \\((.+?)\\): (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/compatible.rs","lineNumber":2875,"sourceCode":"        let response = match self\n            .apply_auth_header(\n                self.http_client().post(&url).json(&request),\n                credential.as_deref(),\n            )\n            .send()\n            .await\n        {\n            Ok(response) => response,\n            Err(chat_error) => {\n                return Err(chat_error.into());\n            }\n        };\n\n        if !response.status().is_success() {\n            let status = response.status();\n            let error = response.text().await?;\n            let sanitized = super::sanitize_api_error(&error);\n            anyhow::bail!(\"{} API error ({status}): {sanitized}\", self.name);\n        }\n\n        let body = response.text().await?;\n        let chat_response = parse_chat_response_body(&self.name, &body)?;\n\n        chat_response\n            .choices\n            .into_iter()\n            .next()\n            .map(|c| {\n                if c.message.tool_calls.is_some()\n                    && c.message\n                        .tool_calls\n                        .as_ref()\n                        .is_some_and(|t: &Vec<_>| !t.is_empty())\n                {\n                    serde_json::to_string(&c.message)\n                        .unwrap_or_else(|_| c.message.effective_content())","sourceCodeStart":2857,"sourceCodeEnd":2893,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/compatible.rs#L2857-L2893","documentation":"chat_with_system on an OpenAI-compatible provider received a non-2xx from the chat completions endpoint. The message includes the provider display name, HTTP status, and the upstream error body after sanitize_api_error scrubs secrets, so the root cause is usually readable in the message itself.","triggerScenarios":"400 malformed or oversized request or unsupported parameter (extra_body fields); 401/403 invalid key; 404 wrong model id or base_url; 429 quota exhausted; 5xx upstream outage - any non-success on the non-streaming chat path.","commonSituations":"Model id typo'd or not enabled for the account; rate limits hit during batch jobs; provider deprecating a model; reverse proxies returning HTML error pages that get sanitized into the message.","solutions":["Read the embedded upstream body - it usually names the exact cause (model not found, quota exceeded, invalid field)","401/403: fix the API key; 404: fix the model id or base_url; 429: back off or raise limits; 400: drop suspect extra_body fields and retry","Retry transient 429/5xx with exponential backoff","If the provider changed its error shape, upgrade zeroclaw-providers"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Validate the cheap stuff before spending a request\nif model.trim().is_empty() {\n    anyhow::bail!(\"model id must not be empty\");\n}\nlet url = reqwest::Url::parse(&chat_url)?; // catches malformed base_url early","typeGuard":null,"tryCatchPattern":"let mut attempt = 0;\nloop {\n    match provider.chat_with_system(None, prompt, model, None).await {\n        Ok(text) => break Ok(text),\n        Err(e) => {\n            let msg = e.to_string();\n            let retryable = msg.contains(\"HTTP 429\") || msg.contains(\"HTTP 5\");\n            if retryable && attempt < 3 {\n                attempt += 1;\n                tokio::time::sleep(std::time::Duration::from_millis(\n                    250u64 * (1 << attempt),\n                )).await;\n                continue;\n            }\n            break Err(e); // 401/403/400 are terminal: fix config or payload\n        }\n    }\n}","preventionTips":["Log the full sanitized body - upstream usually names the cause","Retry only 429/5xx with exponential backoff and jitter; never retry 4xx auth errors","Keep model ids configurable so deprecations are a config change","Run a health/chat smoke test on aliases at startup to catch config drift early"],"tags":["openai-compatible","chat","api-error","http","rust"],"backgroundTag":"llm-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}