sipeed/picoclaw · error
parse response: %w
Error message
parse response: %w
What it means
json.Unmarshal of the HTTP response body into chatResponse failed (llm_client.go:180). The server returned 200 but the body is not valid JSON for this struct — an HTML error page from a reverse proxy, an empty or truncated body, a differently-shaped JSON (missing fields is fine, wrong types are not), or a BOM/whitespace prefix. The error text names byte offset, which localizes the mismatch.
Source
Thrown at cmd/membench/llm_client.go:180
if resp.StatusCode == 429 || resp.StatusCode >= 500 {
lastErr = fmt.Errorf("API error %d: %s", resp.StatusCode, string(respBody))
continue // rate limit or server error → retry
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("API error %d: %s", resp.StatusCode, string(respBody))
}
lastErr = nil
break
}
if lastErr != nil {
return "", fmt.Errorf("after %d retries: %w", c.MaxRetries, lastErr)
}
var chatResp chatResponse
if err := json.Unmarshal(respBody, &chatResp); err != nil {
return "", fmt.Errorf("parse response: %w", err)
}
if len(chatResp.Choices) == 0 {
return "", fmt.Errorf("no choices in response")
}
content := strings.TrimSpace(chatResp.Choices[0].Message.Content)
// Strip any residual <think>...</think> blocks
if idx := strings.Index(content, "</think>"); idx >= 0 {
content = strings.TrimSpace(content[idx+len("</think>"):])
}
// Fallback: GLM/DeepSeek put thinking output in reasoning_content when thinking is enabled
if content == "" && chatResp.Choices[0].Message.ReasoningContent != "" {
content = strings.TrimSpace(chatResp.Choices[0].Message.ReasoningContent)
}
if content == "" {
return "", fmt.Errorf("empty LLM response")
}
return content, nil
}View on GitHub (pinned to 49183d7e8d)
Solutions
- curl -sS $BASE/chat/completions -H 'Content-Type: application/json' -d @req.json and eyeball the actual body
- Fix BaseURL to the exact OpenAI-compatible path (commonly http://host:port/v1)
- Log the first ~512 bytes of respBody on parse failure to see what arrived
- If fields differ, extend chatResponse with json.RawMessage or custom UnmarshalJSON instead of failing
Example fix
// before
if err := json.Unmarshal(respBody, &chatResp); err != nil {
return "", fmt.Errorf("parse response: %w", err)
}
// after
if err := json.Unmarshal(respBody, &chatResp); err != nil {
snippet := string(respBody)
if len(snippet) > 512 { snippet = snippet[:512] }
return "", fmt.Errorf("parse response (status 200, body starts with %q): %w", snippet, err)
} Defensive patterns
Strategy: try-catch
Validate before calling
ct := resp.Header.Get("Content-Type")
if ct != "" && !strings.Contains(ct, "json") {
return "", fmt.Errorf("non-JSON response (%s) from %s — wrong endpoint?", ct, endpoint)
} Try / catch
var chatResp chatResponse
if err := json.Unmarshal(respBody, &chatResp); err != nil {
head := string(respBody)
if len(head) > 256 { head = head[:256] }
return "", fmt.Errorf("parse response (status %d, body %q): %w", resp.StatusCode, head, err)
} Prevention
- Verify BaseURL includes the OpenAI-compatible path (commonly /v1) — a bare host often returns HTML at 200
- Check Content-Type contains application/json before unmarshalling
- Log a body snippet on parse failure; the HTML title usually names the proxy at fault
- Beware providers whose schemas drift — keep tolerant types (json.RawMessage) for volatile fields
When it happens
Trigger: BaseURL points at a web UI or gateway that answers 200 with HTML; proxy (nginx, Cloudflare) intercepting with a status page; response field types differing (e.g. usage as string vs object); chunked body cut off mid-transfer; Ollama version returning a non-OpenAI schema at the wrong path.
Common situations: Using an OpenAI-compatible client against a non-compatible endpoint; BaseURL missing /v1 so the root handler responds 200 HTML; older/newer provider schema drift; corporate proxy rewriting responses.
Related errors
- marshal request: %w
- no choices in response
- decode JSON response: %w
- failed to unmarshal response: %w
- marshal result: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/a85aa0d5db74f34f.
Report an issue: GitHub.