SonarSource/sonarqube · critical · GitlabServerException

Invalid personal access token

Error message

Invalid personal access token

What it means

checkResponseIsSuccessful maps GitLab's HTTP 401 (Unauthorized) — when not identified as revoked or expired — to GitlabServerException 'Invalid personal access token'. The token presented does not authenticate: it is wrong, mistyped, or deleted.

Source

Thrown at server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java:222

    checkResponseIsSuccessful(response, "GitLab Merge Request did not happen, please check your configuration");
  }

  protected static void checkResponseIsSuccessful(Response response, String errorMessage) throws IOException {
    if (!response.isSuccessful()) {
      String body = response.body().string();
      LOG.error("Gitlab API call to [{}] failed with {} http code. gitlab response content : [{}]", response.request().url(), response.code(), body);
      if (isTokenRevoked(response, body)) {
        throw new GitlabServerException(response.code(), "Your GitLab token was revoked");
      } else if (isTokenExpired(response, body)) {
        throw new GitlabServerException(response.code(), "Your GitLab token is expired");
      } else if (isInsufficientScope(response, body)) {
        throw new GitlabServerException(response.code(), "Your GitLab token has insufficient scope");
      } else if (response.code() == HTTP_FORBIDDEN) {
        throw new GitlabServerException(response.code(), "Forbidden access to GitLab. Verify your token's permissions and IP restrictions.");
      } else if (response.code() == HTTP_TOO_MANY_REQUESTS) {
        throw new GitlabServerException(response.code(), "GitLab API rate limit exceeded. Try again later.");
      } else if (response.code() == HTTP_UNAUTHORIZED) {
        throw new GitlabServerException(response.code(), "Invalid personal access token");
      } else if (response.isRedirect()) {
        throw new GitlabServerException(response.code(), "Request was redirected, please provide the correct URL");
      } else {
        throw new GitlabServerException(response.code(), errorMessage);
      }
    }
  }

  private static boolean isTokenRevoked(Response response, String body) {
    if (response.code() == HTTP_UNAUTHORIZED) {
      try {
        Optional<GsonError> gitlabError = GsonError.parseOne(body);
        return gitlabError.map(GsonError::getErrorDescription).map(description -> description.contains("Token was revoked")).orElse(false);
      } catch (JsonParseException e) {
        // nothing to do
      }
    }
    return false;

View on GitHub (pinned to 184c821202)

Solutions

  1. Regenerate the PAT in GitLab and paste it again carefully (no leading/trailing spaces) into SonarQube's GitLab integration settings.
  2. Verify the token works against the configured URL: curl -H 'PRIVATE-TOKEN: <token>' <gitlab-url>/api/v4/user.
  3. Ensure the GitLab URL in SonarQube matches the instance the token was created on.
  4. Use a personal access token (not a deploy token or runner token) with 'api' and 'read_user' scopes.

Example fix

// before (truncated token)
token = "glpat-AbC123";
// after (full token)
token = "glpat-AbC123fullTokenValueFromGitLab";
Defensive patterns

Strategy: validation

Validate before calling

// Validate the token before saving it in SonarQube settings
Response r = call("GET", gitlabUrl + "/api/v4/user", token.trim());
if (r.code() == 401) throw new IllegalArgumentException("Personal access token is invalid for " + gitlabUrl + " — regenerate it and paste without whitespace");

Prevention

When it happens

Trigger: Any GitLab API call via checkResponseIsSuccessful (checkProjectAccess, checkToken, getPersonalAccessTokenInfo, checkWritePermission, createProjectAccessToken) receives 401 whose body does not match the revoked/expired signatures, at GitlabApplicationClient.java:222.

Common situations: Token copy-pasted with extra whitespace or truncation; wrong token kind used (e.g. deploy token instead of PAT); token from a different GitLab instance than the configured URL; token deleted while SonarQube still caches it.

Related errors


AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09). Data as JSON: /api/errors/3ad0c507920b5126. Report an issue: GitHub.