github/github-mcp-server · error

failed to read response body: %w

Error message

failed to read response body: %w

What it means

In get_commit, client.Repositories.GetCommit returned non-200 and the follow-up io.ReadAll(resp.Body) needed for the structured status error failed. The transport read failure replaces the real GitHub status (usually 404 unknown SHA or 422/451 blocked by DMCA/policy).

Source

Thrown at pkg/github/repositories.go:111

			client, err := deps.GetClient(ctx)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
			}
			commit, resp, err := client.Repositories.GetCommit(ctx, owner, repo, sha, opts)
			if err != nil {
				return ghErrors.NewGitHubAPIErrorResponse(ctx,
					fmt.Sprintf("failed to get commit: %s", sha),
					resp,
					err,
				), nil, nil
			}
			defer func() { _ = resp.Body.Close() }()

			if resp.StatusCode != 200 {
				body, err := io.ReadAll(resp.Body)
				if err != nil {
					return nil, nil, fmt.Errorf("failed to read response body: %w", err)
				}
				return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get commit", resp, body), nil, nil
			}

			// Convert to minimal commit
			minimalCommit := convertToMinimalCommit(commit, detail)

			r, err := json.Marshal(minimalCommit)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
			}

			result := utils.NewToolResultText(string(r))
			// Commit content is reachable from the repo's history; in public
			// repos anyone can land it via a PR (untrusted), in private repos
			// only collaborators can (trusted). Confidentiality follows repo
			// visibility.
			result = attachRepoVisibilityIFCLabel(ctx, deps, client, owner, repo, result, ifc.LabelCommitContents)

View on GitHub (pinned to 0ea1f775a7)

Solutions

  1. Retry once — read failures are usually transient connection-reuse races
  2. Verify the SHA exists and is reachable from this repo (it must be referenced by a ref or PR)
  3. Check rate-limit headers on adjacent calls if 403s cluster
  4. Inspect the wrapped error for context.Canceled to rule out client timeouts

Example fix

// before
body, err := io.ReadAll(resp.Body)
if err != nil {
	return nil, nil, fmt.Errorf("failed to read response body: %w", err)
}

// after — keep the status code visible when the body is lost
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if readErr != nil {
	return nil, nil, fmt.Errorf("failed to read commit body (status %d, sha %s): %w", resp.StatusCode, sha, readErr)
}
Defensive patterns

Strategy: retry

Validate before calling

func validSHA(sha string) error {
	if len(sha) < 7 || len(sha) > 40 {
		return fmt.Errorf("suspicious SHA length %d", len(sha))
	}
	for _, r := range sha {
		if !strings.ContainsRune("0123456789abcdefABCDEF", r) {
			return fmt.Errorf("SHA contains non-hex character %q", r)
		}
	}
	return nil
}

Try / catch

res, err := callGetCommit(ctx, owner, repo, sha, detail)
if err != nil && isTransientReadErr(err) {
	res, err = callGetCommit(ctx, owner, repo, sha, detail)
}

Prevention

When it happens

Trigger: GetCommit answers 404 (SHA typo, unreferenced object, fork without the commit), 451 (content blocked), 403 (rate limit), then the error body read breaks via connection reset, HTTP/2 stream error, or context cancellation.

Common situations: Clients passing shortened or malformed SHAs; repos affected by DMCA takediffs returning 451 with large bodies; rate-limited CI jobs where truncated error bodies coincide; proxy interruptions on GHES.

Related errors


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