github/github-mcp-server · error

installation token request failed: %s (reading response: %w)

Error message

installation token request failed: %s (reading response: %w)

What it means

The token endpoint answered with a non-201 status AND reading (up to 512 bytes of) the error body failed — e.g. the connection reset mid-body. The message includes resp.Status and the read error so the status code is never lost. This is the rare degraded branch of the non-201 handler at internal/githubapp/githubapp.go:153-157; the common branch is error 106.

Source

Thrown at internal/githubapp/githubapp.go:156

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, nil)
	if err != nil {
		return nil, fmt.Errorf("creating installation token request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+jwt)
	req.Header.Set("Accept", "application/vnd.github+json")
	req.Header.Set("X-GitHub-Api-Version", "2022-11-28")

	resp, err := s.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("requesting installation token: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusCreated {
		snippet, readErr := io.ReadAll(io.LimitReader(resp.Body, 512))
		if readErr != nil {
			return nil, fmt.Errorf("installation token request failed: %s (reading response: %w)", resp.Status, readErr)
		}
		return nil, fmt.Errorf("installation token request failed: %s: %s", resp.Status, strings.TrimSpace(string(snippet)))
	}

	var body struct {
		Token     string    `json:"token"`
		ExpiresAt time.Time `json:"expires_at"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
		return nil, fmt.Errorf("decoding installation token response: %w", err)
	}
	if body.Token == "" {
		return nil, errors.New("installation token response did not contain a token")
	}
	if body.ExpiresAt.IsZero() {
		return nil, errors.New("installation token response did not contain an expiry")
	}
	return &oauth2.Token{

View on GitHub (pinned to 0ea1f775a7)

Solutions

  1. Read the status code from the message — it usually identifies the real problem even without a body
  2. Reproduce with curl from the same host to see the full response the proxy/host returns
  3. Bypass or fix the intercepting proxy for api.github.com traffic
  4. Retry — mid-body resets are typically transient
Defensive patterns

Strategy: retry

Try / catch

if strings.Contains(err.Error(), "installation token request failed:") {
    // status is in the message even when the body read failed; classify on it
}

Prevention

When it happens

Trigger: GitHub's server (or an intercepting proxy) sends a 4xx/5xx status line, then resets or half-closes the TCP connection before the body arrives, so io.ReadAll(io.LimitReader(resp.Body, 512)) at internal/githubapp/githubapp.go:154 returns a read error. Common with misbehaving transparent proxies and LB idle-timeout kills.

Common situations: Corporate MITM proxy that truncates error responses; a GHES node behind an aggressive load balancer; flaky NAT gateways on cellular/VPN links; connections killed by security appliances after sending status.

Related errors


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