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
- Read the status code from the message — it usually identifies the real problem even without a body
- Reproduce with curl from the same host to see the full response the proxy/host returns
- Bypass or fix the intercepting proxy for api.github.com traffic
- 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
- Bypass MITM proxies for api.github.com so error bodies arrive intact
- Treat mid-body resets as transient: the next AccessToken() call retries the whole request
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
- decoding installation token response: %w
- failed to download logs: %w
- requesting installation token: %w
- callback server: %w
- failed to read response body: %w
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/dc4713930e44b6bb.
Report an issue: GitHub.