Tencent/WeKnora · error
failed to read response: %w
Error message
failed to read response: %w
What it means
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.
Source
Thrown at internal/infrastructure/web_search/keenable.go:112
if p.apiKey != "" {
req.Header.Set("X-API-Key", p.apiKey)
}
resp, err := p.client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to execute request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
respBody, _ := io.ReadAll(resp.Body)
logger.Warnf(ctx, "[WebSearch][Keenable] API returned status %d: %s", resp.StatusCode, string(respBody))
return nil, fmt.Errorf("keenable API returned status %d: %s", resp.StatusCode, string(respBody))
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
var respData keenableSearchResponse
if err := json.Unmarshal(respBody, &respData); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
results := make([]*types.WebSearchResult, 0, len(respData.Results))
for _, item := range respData.Results {
if maxResults > 0 && len(results) >= maxResults {
break
}
// Keenable returns both a short "description" and a longer "snippet"
// excerpt. Prefer the short description as the summary snippet and keep
// the longer excerpt as Content (used by RAG compression). Fall back to
// whichever is present so we never drop the only available text.
snippet := item.Description
if snippet == "" {View on GitHub (pinned to 988cbb0330)
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.
Example fix
// before: fail hard on any read error
if err != nil { return nil, err }
// after: retry transient read failures
body, err := readWithRetry(ctx, client, req, 3)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("keenable read timed out: %w", err)
}
return nil, err
} Defensive patterns
Strategy: retry
Validate before calling
if dl, ok := ctx.Deadline(); !ok || time.Until(dl) < 10*time.Second {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
defer cancel()
} Try / catch
body, err := io.ReadAll(resp.Body)
if err != nil {
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() || errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("transient read failure, retrying: %w", err)
}
return nil, fmt.Errorf("failed to read response: %w", err)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Flaky network or mobile connections; aggressive upstream load balancer timeouts; ctx cancellation/deadline shorter than Keenable's response time; misbehaving corporate proxy.
Related errors
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/e62b00e6d6e98338.
Report an issue: GitHub.