{"record":{"id":"e7fdc089b0007233","repo":"charmbracelet/crush","slug":"failed-to-read-response-body-w-e7fdc0","errorCode":null,"errorMessage":"failed to read response body: %w","messagePattern":"failed to read response body: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/sourcegraph.go","lineNumber":139,"sourceCode":"\t\t\treq.Header.Set(\"User-Agent\", \"crush/1.0\")\n\n\t\t\tresp, err := client.Do(req)\n\t\t\tif err != nil {\n\t\t\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to fetch URL: %w\", err)\n\t\t\t}\n\t\t\tdefer resp.Body.Close()\n\n\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\tbody, _ := io.ReadAll(resp.Body)\n\t\t\t\tif len(body) > 0 {\n\t\t\t\t\treturn fantasy.NewTextErrorResponse(fmt.Sprintf(\"Request failed with status code: %d, response: %s\", resp.StatusCode, string(body))), nil\n\t\t\t\t}\n\n\t\t\t\treturn fantasy.NewTextErrorResponse(fmt.Sprintf(\"Request failed with status code: %d\", resp.StatusCode)), nil\n\t\t\t}\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\tif err != nil {\n\t\t\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to read response body: %w\", err)\n\t\t\t}\n\n\t\t\tvar result map[string]any\n\t\t\tif err = json.Unmarshal(body, &result); err != nil {\n\t\t\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to unmarshal response: %w\", err)\n\t\t\t}\n\n\t\t\tformattedResults, err := formatSourcegraphResults(result, params.ContextWindow, params.Count)\n\t\t\tif err != nil {\n\t\t\t\treturn fantasy.NewTextErrorResponse(\"Failed to format results: \" + err.Error()), nil\n\t\t\t}\n\n\t\t\treturn fantasy.NewTextResponse(formattedResults), nil\n\t\t},\n\t)\n}\n\nfunc formatSourcegraphResults(result map[string]any, contextWindow, maxResults int) (string, error) {","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/sourcegraph.go#L121-L157","documentation":"Wraps an io.ReadAll error while reading a successful (HTTP 200) response body from the Sourcegraph GraphQL API (internal/agent/tools/sourcegraph.go:137-140). The status was OK but the body stream broke mid-read — typically context cancellation, an unexpected EOF from a dropped connection, or an HTTP/2 stream error.","triggerScenarios":"io.ReadAll(resp.Body) errors after a 200 from sourcegraph.com/.api/graphql: the request context deadline fires mid-body, the server or an intermediary closes the connection early (unexpected EOF), or a large result set exceeds what the network can deliver before a timeout.","commonSituations":"Broad queries (e.g. searching a pattern across all of GitHub) returning huge GraphQL payloads on slow links; flaky VPN/corporate proxies truncating large responses; Timeout param set so low that a big body cannot finish transferring within the client's 30s default.","solutions":["Narrow the query (add repo:, file:, or count:) so the response payload is smaller and completes before timeouts.","Increase the http.Client timeout and/or the tool Timeout param for large searches.","Retry once on transient errors; check errors.Is(err, context.DeadlineExceeded) to distinguish timeouts from dropped connections.","Investigate proxy/VPN stability if unexpected EOF recurs on large responses."],"exampleFix":"// before\nparams := SourcegraphParams{Query: \"fmt.Errorf\", Timeout: 5}\n\n// after\nparams := SourcegraphParams{Query: \"fmt.Errorf repo:github.com/charmbracelet/crush\", Timeout: 60}","handlingStrategy":"retry","validationCode":"if params.Timeout > 0 && params.Timeout < 15 {\n    params.Timeout = 15 // avoid cutting large bodies off mid-read\n}","typeGuard":"if errors.Is(err, io.ErrUnexpectedEOF) { /* connection dropped mid-body, retryable */ }\nif errors.Is(err, context.DeadlineExceeded) { /* body too large for timeout */ }","tryCatchPattern":"body, err := io.ReadAll(resp.Body)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.ErrUnexpectedEOF) {\n        // retry once with narrower query / longer timeout\n    }\n    return err\n}","preventionTips":["Scope Sourcegraph queries with repo:/file: filters to keep response bodies small.","Use generous timeouts for broad searches on slow links.","Prefer streaming with a size cap over unbounded ReadAll for very large responses.","Treat repeated unexpected EOF as a proxy/network stability problem, not a code bug."],"tags":["network","http","io","timeout"],"backgroundTag":"response-body-read-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}