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

  1. Retry the tool call once — the underlying list is a read-only GET and transient read failures usually clear
  2. If behind a proxy, raise its response-body timeout / verify it forwards error bodies intact
  3. Check any custom RoundTripper wrapped into the GitHub client for premature body consumption or close
  4. Log resp.StatusCode alongside the error so operators know which status triggered the failed read
  5. 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

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


AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15). Data as JSON: /api/errors/a0f02019d19b5df3. Report an issue: GitHub.