Skip to content

Commit ff416ff

Browse files
committed
fix(api): surface provider error body before attempting completion parse
When a local/proxy OpenAI-compatible backend returns an error object: {"error":{"message":"...","type":"...","code":...}} claw was trying to deserialize it as a ChatCompletionResponse and failing with the cryptic 'failed to parse OpenAI response: missing field id', completely hiding the actual backend error message. Fix: before full deserialization, check if the parsed JSON has an 'error' key and promote it directly to ApiError::Api so the user sees the real error (e.g. 'The number of tokens to keep from the initial prompt is greater than the context length'). Source: devilayu in #claw-code 2026-04-09 — local LM Studio context limit error was invisible; user saw 'missing field id' instead. 159 CLI + 115 api tests pass. Fmt clean.
1 parent 6ac7d8c commit ff416ff

1 file changed

Lines changed: 29 additions & 0 deletions

File tree

rust/crates/api/src/providers/openai_compat.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,35 @@ impl OpenAiCompatClient {
157157
let response = self.send_with_retry(&request).await?;
158158
let request_id = request_id_from_headers(response.headers());
159159
let body = response.text().await.map_err(ApiError::from)?;
160+
// Some backends return {"error":{"message":"...","type":"...","code":...}}
161+
// instead of a valid completion object. Check for this before attempting
162+
// full deserialization so the user sees the actual error, not a cryptic
163+
// "missing field 'id'" parse failure.
164+
if let Ok(raw) = serde_json::from_str::<serde_json::Value>(&body) {
165+
if let Some(err_obj) = raw.get("error") {
166+
let msg = err_obj
167+
.get("message")
168+
.and_then(|m| m.as_str())
169+
.unwrap_or("provider returned an error")
170+
.to_string();
171+
let code = err_obj
172+
.get("code")
173+
.and_then(|c| c.as_u64())
174+
.map(|c| c as u16);
175+
return Err(ApiError::Api {
176+
status: reqwest::StatusCode::from_u16(code.unwrap_or(400))
177+
.unwrap_or(reqwest::StatusCode::BAD_REQUEST),
178+
error_type: err_obj
179+
.get("type")
180+
.and_then(|t| t.as_str())
181+
.map(str::to_owned),
182+
message: Some(msg),
183+
request_id,
184+
body,
185+
retryable: false,
186+
});
187+
}
188+
}
160189
let payload = serde_json::from_str::<ChatCompletionResponse>(&body).map_err(|error| {
161190
ApiError::json_deserialize(self.config.provider_name, &request.model, &body, error)
162191
})?;

0 commit comments

Comments
 (0)