{"record":{"id":"211293869153f2e7","repo":"sigoden/aichat","slug":"error","errorCode":null,"errorMessage":"{error}","messagePattern":"\\{error\\}","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/client/common.rs","lineNumber":528,"sourceCode":"    } else if let Some(error) = data[\"errors\"][0].as_object() {\n        if let (Some(code), Some(message)) = (\n            error.get(\"code\").and_then(|v| v.as_u64()),\n            json_str_from_map(error, \"message\"),\n        ) {\n            bail!(\"{message} (status: {code})\")\n        }\n    } else if let Some(error) = data[0][\"error\"].as_object() {\n        if let (Some(status), Some(message)) = (\n            json_str_from_map(error, \"status\"),\n            json_str_from_map(error, \"message\"),\n        ) {\n            bail!(\"{message} (status: {status})\")\n        }\n    } else if let (Some(detail), Some(status)) = (data[\"detail\"].as_str(), data[\"status\"].as_i64())\n    {\n        bail!(\"{detail} (status: {status})\");\n    } else if let Some(error) = data[\"error\"].as_str() {\n        bail!(\"{error}\");\n    } else if let Some(message) = data[\"message\"].as_str() {\n        bail!(\"{message}\");\n    }\n    bail!(\"Invalid response data: {data} (status: {status})\");\n}\n\npub fn json_str_from_map<'a>(\n    map: &'a serde_json::Map<String, Value>,\n    field_name: &str,\n) -> Option<&'a str> {\n    map.get(field_name).and_then(|v| v.as_str())\n}\n\nasync fn set_client_models_config(client_config: &mut Value, client: &str) -> Result<String> {\n    if let Some(provider) = ALL_PROVIDER_MODELS.iter().find(|v| v.provider == client) {\n        let models: Vec<String> = provider\n            .models\n            .iter()","sourceCodeStart":510,"sourceCodeEnd":546,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/client/common.rs#L510-L546","documentation":"This error is raised by `catch_error` (src/client/common.rs:528) when an upstream LLM API returns a non-2xx HTTP status whose JSON body contains an `error` field that is a plain string (not an object with type/message). The library surfaces that raw string verbatim as the error message. It is the fallback branch for APIs whose error payloads don't match the structured OpenAI/Anthropic shapes checked earlier.","triggerScenarios":"Any call to chat_completions, chat_completions_streaming, embeddings, claude_chat_completions, openai_chat_completions, or openai_embeddings receiving a non-2xx response whose body parses as JSON with `data[\"error\"]` being a JSON string, e.g. `{\"error\": \"Invalid API key\"}` with status 400/401/404/429/500.","commonSituations":"Wrong or expired API key; hitting a non-OpenAI-compatible endpoint; proxies/gateways (e.g. LiteLLM, nginx) returning simple `{\"error\": \"...\"}` bodies; deprecated model names rejected server-side; quota exhausted on a custom provider.","solutions":["Read the message text included in the error — it is the provider's own error string; fix what it names first (usually auth or model name).","Verify the API key is valid and set (config or <CLIENT>_API_KEY env var) with a direct curl to the endpoint.","Check that api_base points at a real OpenAI-compatible endpoint that returns the expected error shapes.","If the provider returns structured errors, ensure the client type matches the provider so the structured branches of catch_error parse them instead."],"exampleFix":"// before: hitting wrong endpoint\nlet client = Client::new(\"openai\", api_base: \"https://example.com/v1\");\n// after: correct endpoint and key\nlet client = Client::new(\"openai\", api_base: \"https://api.openai.com/v1\", api_key: Some(key));","handlingStrategy":"try-catch","validationCode":"// preflight: verify key/endpoint before calling\nlet models = fetch_models(api_base, Some(&api_key)).await?;","typeGuard":"fn is_upstream_error_str(err: &anyhow::Error) -> bool {\n    err.to_string().contains(\"status:\") || !err.to_string().is_empty()\n}","tryCatchPattern":"match client.chat_completions(&req).await {\n    Err(e) if e.to_string().contains(\"status: 401\") => refresh_api_key_and_retry(),\n    Err(e) => eprintln!(\"request failed: {e}\"),\n    Ok(r) => handle(r),\n}","preventionTips":["Validate the API key with a cheap /v1/models call before heavy requests.","Point api_base at a real OpenAI-compatible endpoint, not a proxy UI page.","Log the failing response body at debug level to see the provider's error string.","Use backoff retry only for 429/5xx messages."],"tags":["http-error","api-client","rust","upstream-provider"],"backgroundTag":"http-error-response","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}