{"record":{"id":"870483d4d367eb60","repo":"Tencent/WeKnora","slug":"failed-to-unmarshal-response-w-870483","errorCode":null,"errorMessage":"failed to unmarshal response: %w","messagePattern":"failed to unmarshal response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/web_search/keenable.go","lineNumber":117,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to execute request: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\trespBody, _ := io.ReadAll(resp.Body)\n\t\tlogger.Warnf(ctx, \"[WebSearch][Keenable] API returned status %d: %s\", resp.StatusCode, string(respBody))\n\t\treturn nil, fmt.Errorf(\"keenable API returned status %d: %s\", resp.StatusCode, string(respBody))\n\t}\n\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read response: %w\", err)\n\t}\n\n\tvar respData keenableSearchResponse\n\tif err := json.Unmarshal(respBody, &respData); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal response: %w\", err)\n\t}\n\n\tresults := make([]*types.WebSearchResult, 0, len(respData.Results))\n\tfor _, item := range respData.Results {\n\t\tif maxResults > 0 && len(results) >= maxResults {\n\t\t\tbreak\n\t\t}\n\t\t// Keenable returns both a short \"description\" and a longer \"snippet\"\n\t\t// excerpt. Prefer the short description as the summary snippet and keep\n\t\t// the longer excerpt as Content (used by RAG compression). Fall back to\n\t\t// whichever is present so we never drop the only available text.\n\t\tsnippet := item.Description\n\t\tif snippet == \"\" {\n\t\t\tsnippet = item.Snippet\n\t\t}\n\t\tresult := &types.WebSearchResult{\n\t\t\tTitle:   item.Title,\n\t\t\tURL:     item.URL,","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/web_search/keenable.go#L99-L135","documentation":"Returned by KeenableProvider.Search when json.Unmarshal cannot decode the API response body into keenableSearchResponse. The body was received and read successfully, but it is not the JSON shape the provider expects — often HTML from an error/login page, truncated JSON, or a schema change at the Keenable API. The json error is wrapped for inspection.","triggerScenarios":"Search (internal/infrastructure/web_search/keenable.go:117) read a 200 body but json.Unmarshal(respBody, &respData) fails — non-JSON HTML body, malformed/truncated JSON, or fields whose types no longer match keenableSearchResponse.","commonSituations":"Proxy/captive portal returning HTML with status 200; Keenable API contract change after a version bump; CDN error interstitial; body cut off mid-stream by a short client timeout.","solutions":["Log/print the raw respBody on failure to see whether it is HTML, empty, or truncated JSON.","If the body is HTML, look for an intercepting proxy, captive portal, or wrong baseURL in the provider config.","Compare the actual JSON against the keenableSearchResponse struct fields/types; update the struct if the API schema changed.","Pin or update the provider version to match the current Keenable API contract."],"exampleFix":"// before: opaque unmarshal failure\nif err := json.Unmarshal(respBody, &respData); err != nil {\n    return nil, fmt.Errorf(\"failed to unmarshal response: %w\", err)\n}\n\n// after: include a body snippet for diagnosis\nif err := json.Unmarshal(respBody, &respData); err != nil {\n    return nil, fmt.Errorf(\"failed to unmarshal response: %w (body: %.200s)\", err, string(respBody))\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isLikelyJSON(b []byte) bool {\n    t := bytes.TrimSpace(b)\n    return len(t) > 0 && (t[0] == '{' || t[0] == '[')\n}\n\n// call before unmarshalling:\nif !isLikelyJSON(respBody) {\n    return nil, fmt.Errorf(\"non-JSON response body: %.200s\", string(respBody))\n}","tryCatchPattern":"if err := json.Unmarshal(respBody, &respData); err != nil {\n    log.Printf(\"keenable unmarshal failed: %v, body=%.500s\", err, string(respBody))\n    return nil, fmt.Errorf(\"failed to unmarshal response: %w\", err)\n}","preventionTips":["Include a raw-body snippet in unmarshal error logs to speed up diagnosis.","Add a contract/integration test that decodes a recorded live Keenable response.","Watch provider changelogs for response schema changes and update structs promptly.","Check for proxies/interceptors in the deployment environment that may replace JSON with HTML."],"tags":["json","unmarshal","web-search","schema"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}