{"record":{"id":"e62b00e6d6e98338","repo":"Tencent/WeKnora","slug":"failed-to-read-response-w","errorCode":null,"errorMessage":"failed to read response: %w","messagePattern":"failed to read response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/web_search/keenable.go","lineNumber":112,"sourceCode":"\tif p.apiKey != \"\" {\n\t\treq.Header.Set(\"X-API-Key\", p.apiKey)\n\t}\n\n\tresp, err := p.client.Do(req)\n\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 == \"\" {","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/web_search/keenable.go#L94-L130","documentation":"Returned by KeenableProvider.Search when io.ReadAll(resp.Body) fails while reading the successful HTTP response body. This means the connection broke or timed out mid-transfer after the server already sent a 200 status, so the body could not be fully read. The underlying read error is wrapped with %w for errors.Is/errors.As inspection.","triggerScenarios":"Search (internal/infrastructure/web_search/keenable.go:112) got a 200 response but io.ReadAll(resp.Body) returned an error — connection reset by peer, context deadline exceeded mid-body, TLS truncation, or proxy dropping the stream.","commonSituations":"Flaky network or mobile connections; aggressive upstream load balancer timeouts; ctx cancellation/deadline shorter than Keenable's response time; misbehaving corporate proxy.","solutions":["Unwrap with errors.Is(err, context.DeadlineExceeded) / net.Error.Timeout() to distinguish timeouts from resets, and increase the client timeout or context deadline.","Retry the request with exponential backoff — this is typically a transient network fault.","Verify network path: disable or fix proxies/VPNs that may be truncating the response.","Check server-side: if it reproduces for large responses only, reduce maxResults to shrink the body."],"exampleFix":"// before: fail hard on any read error\nif err != nil { return nil, err }\n\n// after: retry transient read failures\nbody, err := readWithRetry(ctx, client, req, 3)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        return nil, fmt.Errorf(\"keenable read timed out: %w\", err)\n    }\n    return nil, err\n}","handlingStrategy":"retry","validationCode":"if dl, ok := ctx.Deadline(); !ok || time.Until(dl) < 10*time.Second {\n    var cancel context.CancelFunc\n    ctx, cancel = context.WithTimeout(ctx, 30*time.Second)\n    defer cancel()\n}","typeGuard":null,"tryCatchPattern":"body, err := io.ReadAll(resp.Body)\nif err != nil {\n    var ne net.Error\n    if errors.As(err, &ne) && ne.Timeout() || errors.Is(err, context.DeadlineExceeded) {\n        return nil, fmt.Errorf(\"transient read failure, retrying: %w\", err)\n    }\n    return nil, fmt.Errorf(\"failed to read response: %w\", err)\n}","preventionTips":["Set a client timeout generous enough for the largest expected response.","Always retry idempotent search GETs/POSTs a couple of times with backoff.","Keep contexts alive for the whole request; don't cancel the parent early.","Test under flaky-network conditions (e.g. tc netem) before deploying."],"tags":["network","io","http","web-search"],"backgroundTag":"response-body-read-error","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}