github/github-mcp-server · error
failed to read response body: %w
Error message
failed to read response body: %w
What it means
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.
Source
Thrown at pkg/github/issue_dependencies.go:121
return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
}
})
st.FeatureFlagEnable = FeatureFlagIssueDependencies
return st
}
// GetIssueBlockedBy lists the issues that block the given issue.
func GetIssueBlockedBy(ctx context.Context, client *github.Client, owner, repo string, issueNumber int, opts *github.ListOptions) (*mcp.CallToolResult, error) {
issues, resp, err := client.Issues.ListBlockedBy(ctx, owner, repo, int64(issueNumber), opts)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list blocked-by issues", resp, err), nil
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to list blocked-by issues", resp, body), nil
}
return dependencyReadResult(issues, resp), nil
}
// GetIssueBlocking lists the issues that the given issue blocks.
func GetIssueBlocking(ctx context.Context, client *github.Client, owner, repo string, issueNumber int, opts *github.ListOptions) (*mcp.CallToolResult, error) {
issues, resp, err := client.Issues.ListBlocking(ctx, owner, repo, int64(issueNumber), opts)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list blocking issues", resp, err), nil
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)View on GitHub (pinned to 0ea1f775a7)
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
Example fix
// before
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
// after: degrade gracefully, keep the status context
body, rerr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if rerr != nil {
body = []byte(fmt.Sprintf("(body unreadable: %v; status %d)", rerr, resp.StatusCode))
}
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to list blocked-by issues", resp, body), nil Defensive patterns
Strategy: retry
Validate before calling
// cheap sanity checks before the call
if client == nil || client.Client() == nil || client.Client().Transport == nil {
return errors.New("github client misconfigured")
} Try / catch
issues, resp, err := client.Issues.ListBlockedBy(ctx, owner, repo, int64(n), opts)
if err != nil {
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list blocked-by issues", resp, err), nil
}
// non-200 path: read defensively, degrade instead of failing
body, rerr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if rerr != nil {
body = nil // fall back to status-only error; safe to retry the GET later
}
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to list blocked-by issues", resp, body), nil Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- GitHub App authentication requires a private key: set GITHUB
- GitHub App private key is required (GITHUB_APP_PRIVATE_KEY_P
- no PEM block found in private key
- failed to download logs: %w
- failed to read response body: %w
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/a0f02019d19b5df3.
Report an issue: GitHub.