{"record":{"id":"c7a8c97d14e9277e","repo":"Tencent/WeKnora","slug":"failed-to-decode-api-response-w","errorCode":null,"errorMessage":"failed to decode API response: %w","messagePattern":"failed to decode API response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/web_search/duckduckgo.go","lineNumber":178,"sourceCode":"\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn nil, fmt.Errorf(\"duckduckgo API returned status %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar apiResponse struct {\n\t\tAbstractText  string `json:\"AbstractText\"`\n\t\tAbstractURL   string `json:\"AbstractURL\"`\n\t\tHeading       string `json:\"Heading\"`\n\t\tRelatedTopics []struct {\n\t\t\tFirstURL string `json:\"FirstURL\"`\n\t\t\tText     string `json:\"Text\"`\n\t\t} `json:\"RelatedTopics\"`\n\t\tResults []struct {\n\t\t\tFirstURL string `json:\"FirstURL\"`\n\t\t\tText     string `json:\"Text\"`\n\t\t} `json:\"Results\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode API response: %w\", err)\n\t}\n\n\tresults := make([]*types.WebSearchResult, 0, maxResults)\n\tif apiResponse.AbstractText != \"\" && apiResponse.AbstractURL != \"\" {\n\t\tresults = append(results, &types.WebSearchResult{\n\t\t\tTitle:   apiResponse.Heading,\n\t\t\tURL:     apiResponse.AbstractURL,\n\t\t\tSnippet: apiResponse.AbstractText,\n\t\t\tSource:  \"duckduckgo\",\n\t\t})\n\t}\n\tfor _, topic := range apiResponse.RelatedTopics {\n\t\tif len(results) >= maxResults {\n\t\t\tbreak\n\t\t}\n\t\tif topic.Text != \"\" && topic.FirstURL != \"\" {\n\t\t\tresults = append(results, &types.WebSearchResult{\n\t\t\t\tTitle:   extractTitle(topic.Text),","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/web_search/duckduckgo.go#L160-L196","documentation":"Returned by searchAPI when the JSON response body from the DuckDuckGo instant-answer API cannot be decoded into the anonymous response struct. Means DuckDuckGo returned 200 with malformed JSON, an HTML error page, or an empty/truncated body — the search fails for this call even though HTTP reported success.","triggerScenarios":"Calling Search -> searchAPI when the response is HTML (a block/captcha page), truncated, an error JSON with a different schema, or content-encoding mishandled by a proxy.","commonSituations":"DuckDuckGo serving a bot-detection HTML page instead of JSON, proxies rewriting responses, API schema changes, reading a 200 body that is empty or cut off.","solutions":["Log the first bytes of resp.Body on decode failure to see what was actually returned","Check for HTML/bot-block pages and switch IP/User-Agent","Verify the struct tags still match the current DuckDuckGo API schema","Use json.Unmarshal on a bounded-read body to get a clearer error offset"],"exampleFix":"// before\nif err := json.NewDecoder(resp.Body).Decode(&apiResponse); err != nil {\n    return nil, fmt.Errorf(\"failed to decode API response: %w\", err)\n}\n// after\nbody, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))\nif err := json.Unmarshal(body, &apiResponse); err != nil {\n    return nil, fmt.Errorf(\"failed to decode API response (body head: %.200s): %w\", string(body), err)\n}","handlingStrategy":"type-guard","validationCode":"ct := resp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"application/json\") {\n    return fmt.Errorf(\"expected JSON, got %s\", ct)\n}","typeGuard":"func isJSONBody(head []byte) bool {\n    h := bytes.TrimLeft(head, \" \\t\\r\\n\")\n    return len(h) > 0 && (h[0] == '{' || h[0] == '[')\n}","tryCatchPattern":"results, err := provider.Search(ctx, query, 10, false)\nif err != nil && strings.Contains(err.Error(), \"failed to decode API response\") {\n    log.Printf(\"duckduckgo returned non-JSON (bot block?): %v\", err)\n    return fallbackProvider.Search(ctx, query, 10, false)\n}","preventionTips":["Inspect Content-Type before decoding","Log a bounded snippet of the body on decode failure","Keep struct tags in sync with the documented API schema","Detect HTML/captcha pages and rotate IP or User-Agent"],"tags":["json","decoding","api","schema","go"],"backgroundTag":"json-decode-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}