github/github-mcp-server · error
decoding installation token response: %w
Error message
decoding installation token response: %w
What it means
The endpoint returned 201 Created but the JSON decoder could not parse the body: the response is not valid JSON matching {token, expires_at}. Since genuine GitHub responses are well-formed JSON, this points at an intercepting proxy returning HTML, a GHES misconfiguration, or a body truncated mid-transfer. The %w wrap exposes the encoding/json error (e.g. 'invalid character < looking for beginning of value').
Source
Thrown at internal/githubapp/githubapp.go:166
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{
AccessToken: body.Token,
TokenType: "token",
Expiry: body.ExpiresAt.Add(-refreshBuffer),
}, nil
}
// Provider caches and refreshes GitHub App installation access tokens.
type Provider struct {
source oauth2.TokenSource
logger *slog.LoggerView on GitHub (pinned to 0ea1f775a7)
Solutions
- Read the wrapped json error: 'invalid character <' means HTML (proxy/captive portal) — inspect what actually answers with curl -k -v
- Set NO_PROXY for the API host or fix HTTPS_PROXY so the GitHub endpoint is not MITM'd
- For GHES, confirm BaseRESTURL is exactly https://HOST/api/v3/ and the appliance version is current
- Retry on 'unexpected EOF' — truncation is often transient
Defensive patterns
Strategy: try-catch
Try / catch
if strings.Contains(err.Error(), "invalid character") {
// HTML body: a proxy/captive portal answered — inspect with curl, fix proxy config
} Prevention
- Add api.github.com / the GHES host to NO_PROXY when an HTTPS MITM proxy is present
- Pin BaseRESTURL to the documented GHES path https://HOST/api/v3/ and keep GHES updated
When it happens
Trigger: A captive portal / corporate MITM proxy answers 201 with an HTML page; a GHES appliance returns a non-JSON success payload for the api/v3 path; a body larger than the connection's content-length that got cut, causing 'unexpected EOF' in json.NewDecoder(...).Decode at internal/githubapp/githubapp.go:165.
Common situations: Guest/hotel Wi-Fi captive portal intercepting HTTPS via an installed trust root; an internal API gateway fronting GHES that rewrites responses; flaky links truncating bodies; HTTP/2 downgrade middleboxes corrupting framing.
Related errors
- requesting installation token: %w
- installation token request failed: %s (reading response: %w)
- failed to download logs: %w
- requesting device code: %w
- failed to read response body: %w
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/e01fac95a6683fc1.
Report an issue: GitHub.