Tencent/WeKnora · error
failed to unmarshal Exa response: %w
Error message
failed to unmarshal Exa response: %w
What it means
The Exa provider failed to decode the HTTP response body into exaSearchResponse via json.Unmarshal. This means Exa returned a 2xx response whose body is not the expected JSON shape — commonly HTML (an error/interstitial page), an empty body, or a schema that changed.
Source
Thrown at internal/infrastructure/web_search/exa.go:111
logger.Infof(ctx, "[WebSearch][Exa] query=%q maxResults=%d url=%s", query, maxResults, p.baseURL)
resp, err := p.client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to execute Exa request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, maxExaResponseBytes))
if err != nil {
return nil, fmt.Errorf("failed to read Exa response: %w", err)
}
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
logger.Warnf(ctx, "[WebSearch][Exa] API returned status %d: %s", resp.StatusCode, string(body))
return nil, fmt.Errorf("exa API returned status %d: %s", resp.StatusCode, string(body))
}
var data exaSearchResponse
if err := json.Unmarshal(body, &data); err != nil {
return nil, fmt.Errorf("failed to unmarshal Exa response: %w", err)
}
if data.Error != "" {
return nil, fmt.Errorf("exa API error: %s", data.Error)
}
results := make([]*types.WebSearchResult, 0, len(data.Results))
for _, item := range data.Results {
if len(results) >= maxResults {
break
}
snippet := strings.TrimSpace(strings.Join(item.Highlights, "\n"))
content := truncateExaText(strings.TrimSpace(item.Text), maxExaContentRunes)
if snippet == "" {
snippet = truncateExaText(content, 500)
}
result := &types.WebSearchResult{Title: item.Title, URL: item.URL, Snippet: snippet, Content: content, Source: "exa"}
if includeDate && item.PublishedDate != "" {
if publishedAt, err := time.Parse(time.RFC3339, item.PublishedDate); err == nil {View on GitHub (pinned to 988cbb0330)
Solutions
- Log/capture the raw response body to see what was actually returned (often HTML from a proxy).
- Verify no proxy or middleware is rewriting the response; bypass the proxy to test.
- Pin/check the Exa API version and confirm your provider struct fields match the current response schema.
- Add a content-type check before unmarshalling to fail with a clearer message.
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: hit a cheap Exa endpoint and check Content-Type is application/json
resp, _ := http.Get(baseURL)
if !strings.Contains(resp.Header.Get("Content-Type"), "application/json") {
return errors.New("exa response is not JSON — check proxy")
} Try / catch
if err != nil && strings.Contains(err.Error(), "failed to unmarshal Exa response") {
// capture raw body via logs and inspect; fall back to another provider
return fallbackProvider.Search(ctx, q, n, false)
} Prevention
- Check response Content-Type before decoding
- Keep the exaSearchResponse struct in sync with the Exa API docs
- Pin the Exa API version if versioned endpoints are available
- Log raw bodies on decode failure to spot HTML/proxy interstitials
When it happens
Trigger: json.Unmarshal on the (already 2xx) Exa response body returns an error — invalid JSON syntax, unexpected types, or empty body despite HTTP 200.
Common situations: A proxy or captive portal returning HTML with status 200; Exa API version drift changing field types; truncation issues; network middleware injecting content.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- unmarshal page: %w
- exa API returned status %d: %s
- exa API error: %s
- failed to unmarshal response: %w
- failed to unmarshal Metaso response: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/97fe39117a1ed048.
Report an issue: GitHub.