{"record":{"id":"a0f02019d19b5df3","repo":"github/github-mcp-server","slug":"failed-to-read-response-body-w","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":"pkg/github/issue_dependencies.go","lineNumber":121,"sourceCode":"\t\t\t\treturn utils.NewToolResultError(fmt.Sprintf(\"unknown method: %s\", method)), nil, nil\n\t\t\t}\n\t\t})\n\tst.FeatureFlagEnable = FeatureFlagIssueDependencies\n\treturn st\n}\n\n// GetIssueBlockedBy lists the issues that block the given issue.\nfunc GetIssueBlockedBy(ctx context.Context, client *github.Client, owner, repo string, issueNumber int, opts *github.ListOptions) (*mcp.CallToolResult, error) {\n\tissues, resp, err := client.Issues.ListBlockedBy(ctx, owner, repo, int64(issueNumber), opts)\n\tif err != nil {\n\t\treturn ghErrors.NewGitHubAPIErrorResponse(ctx, \"failed to list blocked-by issues\", resp, err), nil\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read response body: %w\", err)\n\t\t}\n\t\treturn ghErrors.NewGitHubAPIStatusErrorResponse(ctx, \"failed to list blocked-by issues\", resp, body), nil\n\t}\n\treturn dependencyReadResult(issues, resp), nil\n}\n\n// GetIssueBlocking lists the issues that the given issue blocks.\nfunc GetIssueBlocking(ctx context.Context, client *github.Client, owner, repo string, issueNumber int, opts *github.ListOptions) (*mcp.CallToolResult, error) {\n\tissues, resp, err := client.Issues.ListBlocking(ctx, owner, repo, int64(issueNumber), opts)\n\tif err != nil {\n\t\treturn ghErrors.NewGitHubAPIErrorResponse(ctx, \"failed to list blocking issues\", resp, err), nil\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read response body: %w\", err)","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/pkg/github/issue_dependencies.go#L103-L139","documentation":"In GetIssueBlockedBy, when client.Issues.ListBlockedBy returns a non-200 response (and err == nil), the handler reads resp.Body with io.ReadAll to include GitHub's error payload in a status-error tool result. This wrap fires when that read fails: the connection was reset mid-body, a proxy truncated the stream, the body was already consumed/closed, or context cancellation arrived between response headers and body read. It replaces the more useful API status error with a transport-level read error.","triggerScenarios":"GitHub or an intermediary (corporate proxy, LB idle timeout) closes the connection after sending headers but before the body completes for 4xx/5xx responses; resp.Body already drained by earlier error handling (e.g. a wrapped client that reads and closes bodies); deadline exceeded via ctx cancellation landing during ReadAll; HTTP/2 stream resets (RST_STREAM) on error responses.","commonSituations":"Flaky networks and mobile clients; aggressive proxy body-size or timeout policies; custom http.Transports in front of go-github that consume response bodies; GHES behind misconfigured load balancers; retry storms amplifying resets.","solutions":["Retry the tool call once — the underlying list is a read-only GET and transient read failures usually clear","If behind a proxy, raise its response-body timeout / verify it forwards error bodies intact","Check any custom RoundTripper wrapped into the GitHub client for premature body consumption or close","Log resp.StatusCode alongside the error so operators know which status triggered the failed read","As a code hardening option, fall back to a status-only error (drop the body) when ReadAll fails, instead of failing the whole result"],"exampleFix":"// before\nbody, err := io.ReadAll(resp.Body)\nif err != nil {\n\treturn nil, fmt.Errorf(\"failed to read response body: %w\", err)\n}\n\n// after: degrade gracefully, keep the status context\nbody, rerr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))\nif rerr != nil {\n\tbody = []byte(fmt.Sprintf(\"(body unreadable: %v; status %d)\", rerr, resp.StatusCode))\n}\nreturn ghErrors.NewGitHubAPIStatusErrorResponse(ctx, \"failed to list blocked-by issues\", resp, body), nil","handlingStrategy":"retry","validationCode":"// cheap sanity checks before the call\nif client == nil || client.Client() == nil || client.Client().Transport == nil {\n\treturn errors.New(\"github client misconfigured\")\n}","typeGuard":null,"tryCatchPattern":"issues, resp, err := client.Issues.ListBlockedBy(ctx, owner, repo, int64(n), opts)\nif err != nil {\n\treturn ghErrors.NewGitHubAPIErrorResponse(ctx, \"failed to list blocked-by issues\", resp, err), nil\n}\n// non-200 path: read defensively, degrade instead of failing\nbody, rerr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))\nif rerr != nil {\n\tbody = nil // fall back to status-only error; safe to retry the GET later\n}\nreturn ghErrors.NewGitHubAPIStatusErrorResponse(ctx, \"failed to list blocked-by issues\", resp, body), nil","preventionTips":["Retry once with backoff on read failures — the list call is an idempotent GET","Raise proxy/LB response-body timeouts if error bodies are truncated mid-stream","Avoid wrapping RoundTrippers that consume or close resp.Body before the handler","Log resp.StatusCode with the read error to identify which status triggered it"],"tags":["go","network","http","transient","issue-dependencies"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}