{"record":{"id":"edacbc26c258ee7d","repo":"github/github-mcp-server","slug":"installation-token-request-failed-s-s","errorCode":null,"errorMessage":"installation token request failed: %s: %s","messagePattern":"installation token request failed: (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/githubapp/githubapp.go","lineNumber":158,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"creating installation token request: %w\", err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+jwt)\n\treq.Header.Set(\"Accept\", \"application/vnd.github+json\")\n\treq.Header.Set(\"X-GitHub-Api-Version\", \"2022-11-28\")\n\n\tresp, err := s.httpClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"requesting installation token: %w\", err)\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\tif resp.StatusCode != http.StatusCreated {\n\t\tsnippet, readErr := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\tif readErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"installation token request failed: %s (reading response: %w)\", resp.Status, readErr)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"installation token request failed: %s: %s\", resp.Status, strings.TrimSpace(string(snippet)))\n\t}\n\n\tvar body struct {\n\t\tToken     string    `json:\"token\"`\n\t\tExpiresAt time.Time `json:\"expires_at\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&body); err != nil {\n\t\treturn nil, fmt.Errorf(\"decoding installation token response: %w\", err)\n\t}\n\tif body.Token == \"\" {\n\t\treturn nil, errors.New(\"installation token response did not contain a token\")\n\t}\n\tif body.ExpiresAt.IsZero() {\n\t\treturn nil, errors.New(\"installation token response did not contain an expiry\")\n\t}\n\treturn &oauth2.Token{\n\t\tAccessToken: body.Token,\n\t\tTokenType:   \"token\",","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/internal/githubapp/githubapp.go#L140-L176","documentation":"The installation-token request completed but GitHub returned a status other than 201 Created; the message embeds the HTTP status plus the first 512 bytes of GitHub's JSON error body (e.g. 'Bad credentials', 'Not Found', 'Integration must acquire access tokens'). This is the primary channel for API-level auth/config failures in the githubapp package — the status + snippet pinpoint which credential is wrong.","triggerScenarios":"401: the JWT signature does not match AppID (key from a different app), the .pem was regenerated after download, or the AppID is wrong. 403: IP not allow-listed or SSO enforcement. 404: InstallationID does not exist for this app (wrong number or app not installed on the target org/repo). 422: malformed request path built from a broken InstallationID. All surface via the fmt.Errorf at internal/githubapp/githubapp.go:158.","commonSituations":"App owner regenerated the private key in GitHub settings but the server still loads the old .pem; AppID/App installation ID mixed up between apps; the app was uninstalled from the organization (404); GitHub Enterprise Server version predating the access_tokens endpoint; org SAML SSO session expired for the installation.","solutions":["401: verify the AppID matches the app whose .pem you load; if the key was regenerated in the UI, download the new .pem and update GITHUB_APP_PRIVATE_KEY_PATH","404: confirm the app is installed on the target org/repo and copy the installation ID from the app's install page URL (/installations/{ID})","403: check IP allow-list / SSO enforcement policies for the installation","Search the snippet text in GitHub's REST docs for the exact condition (the message is GitHub's own documentation field)"],"exampleFix":"// before: AppID and key belong to different apps\n//   error: installation token request failed: 401 Unauthorized: {\"message\":\"Bad credentials\"...}\n\n// after: align them\n//   GITHUB_APP_ID=123456  (from https://github.com/settings/apps/<app> -> About)\n//   GITHUB_APP_INSTALLATION_ID from https://github.com/settings/installations/<ID>\n//   GITHUB_APP_PRIVATE_KEY_PATH=./<app>.pem downloaded fresh from the same app page","handlingStrategy":"try-catch","validationCode":"// fail-fast credential probe: a 401 on this unauthenticated call proves reachability,\n// while a minted-JWT 401 proves the key/AppID pair is wrong\nfunc probeToken(cfg githubapp.Config) error {\n    p, err := githubapp.NewProvider(cfg, slog.Default())\n    if err != nil { return err }\n    if p.AccessToken() == \"\" { return errors.New(\"token mint failed; see logs\") }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"// status-code dispatch on the wrapped message\nmsg := err.Error()\nswitch {\ncase strings.Contains(msg, \"401\"): // key/AppID mismatch — refresh the .pem\ncase strings.Contains(msg, \"404\"): // installation ID wrong or app uninstalled\ncase strings.Contains(msg, \"403\"): // policy: IP allow-list / SSO\n}","preventionTips":["Keep AppID, installation ID, and .pem sourced from the same app page in one config unit","When regenerating a key in GitHub settings, deploy the new .pem atomically with (or before) the config that references it","Run a startup token probe so credential drift fails the deploy, not the first user request"],"tags":["github-api","authentication","configuration","http-status"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}