{"record":{"id":"75aa9c38b74a3548","repo":"SonarSource/sonarqube","slug":"failed-to-mint-a-github-installation-token-for-pro","errorCode":null,"errorMessage":"Failed to mint a GitHub installation token for project '%s': GitHub App API call failed","messagePattern":"Failed to mint a GitHub installation token for project '(.+?)': GitHub App API call failed","errorType":"http","errorClass":"ServerException","httpStatus":500,"severity":"error","filePath":"server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubInstallationTokenProviderImpl.java","lineNumber":142,"sourceCode":"      // above, this isn't a \"not bound\" case — the binding exists, its GitHub App configuration is\n      // just broken (bad credentials, missing permissions, unreachable API, ...). Wrapping instead of\n      // rethrowing as-is adds context in one throw (S2139) while still getting the caller a distinct\n      // 400 instead of the same 404 as a genuinely unbound project.\n      throw new IllegalArgumentException(\n        format(\"Cannot mint a GitHub installation token for project '%s': invalid GitHub App configuration: %s\", safeProjectKey, e.getMessage()), e);\n    }\n\n    Optional<Long> installationId = githubApplicationClient.getInstallationId(githubAppConfiguration, almRepo);\n    if (installationId.isEmpty()) {\n      LOG.warn(\"Cannot mint a GitHub installation token for project '{}': GitHub App is not installed on repository '{}'\", safeProjectKey, safeAlmRepo);\n      return Optional.empty();\n    }\n\n    String repositoryName = bareRepositoryName(almRepo);\n    Optional<ExpiringAppInstallationToken> token = githubApplicationClient.createAppInstallationToken(githubAppConfiguration, installationId.get(), repositoryName);\n    if (token.isEmpty()) {\n      LOG.warn(\"Failed to mint a GitHub installation token for project '{}' (repository '{}')\", safeProjectKey, safeAlmRepo);\n      throw new ServerException(HTTP_INTERNAL_ERROR,\n        format(\"Failed to mint a GitHub installation token for project '%s': GitHub App API call failed\", safeProjectKey));\n    }\n\n    return Optional.of(new GithubInstallationToken(\n      token.get().getValue(), token.get().getExpiresAt().format(ISO_OFFSET_DATE_TIME)));\n  }\n\n  /**\n   * Strips CR/LF from user-controlled values (project key, ALM repo slug) before logging them, so a\n   * crafted value cannot forge extra log lines/entries (CWE-117).\n   */\n  private static String sanitizeForLog(String value) {\n    return CRLF_PATTERN.matcher(value).replaceAll(\"_\");\n  }\n\n  /**\n   * GitHub's installation-token \"repositories\" scoping parameter expects the bare repository name\n   * (no {@code owner/} prefix), unlike {@code almRepo} which is stored as {@code owner/repo}.","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/github/GithubInstallationTokenProviderImpl.java#L124-L160","documentation":"GithubInstallationTokenProviderImpl.mint() calls githubApplicationClient.createAppInstallationToken(); when the client returns Optional.empty() — the GitHub App API call did not yield a token — it logs a warning and throws ServerException(HTTP_INTERNAL_ERROR, 'Failed to mint ... GitHub App API call failed'). This signals an upstream GitHub API failure rather than a client misconfiguration.","triggerScenarios":"GitHub API call to create the app installation token fails or returns no token while configuration and installation ID are valid — GitHub 5xx responses, rate limiting on the App, transient network errors between SonarQube and github.com (or GHES).","commonSituations":"GitHub incidents/outages; secondary rate limits hit by automated token-minting across many repos; GHES under maintenance; proxy timeouts.","solutions":["Retry the operation after a short backoff — mint failures here are often transient GitHub-side issues","Check GitHub status / GHES health and whether the App is hitting rate limits; reduce mint frequency or add caching of installation tokens","Verify SonarQube-to-GitHub connectivity (proxy, TLS) with a direct API probe using the App credentials","Inspect SonarQube server logs around the WARN line for the underlying HTTP error detail"],"exampleFix":"// before\ntoken = client.createAppInstallationToken(cfg, installationId, repo)  // once, no retry\n// after\nretry with exponential backoff (e.g. 3 attempts) and cache the token until near its expiry","handlingStrategy":"retry","validationCode":"// can't fully pre-validate an upstream outage; probe GitHub health first\nconst probe = await fetch(`${githubApiUrl}/api/v3/rate_limit`, { /* app creds */ }).catch(() => null);\nif (!probe || !probe.ok) throw new Error('GitHub API unreachable/rate-limited; defer token minting');","typeGuard":"null","tryCatchPattern":"try {\n  const t = githubInstallationTokenProvider.getNewInstallationToken(projectKey);\n} catch (ServerException e) {\n  if (e.getStatus() == 500 && e.getMessage().contains(\"GitHub App API call failed\")) {\n    // exponential backoff retry, then surface as transient\n  } else throw e;\n}","preventionTips":["Cache installation tokens until near expiry instead of minting per request","Add exponential-backoff retries for GitHub API operations","Monitor GitHub status/GHES maintenance windows for correlation","Watch SonarQube server logs (WARN on mint failure) for rate-limit signals"],"tags":["github","http","upstream"],"backgroundTag":"upstream-api-error","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}