{"record":{"id":"3ad0c507920b5126","repo":"SonarSource/sonarqube","slug":"invalid-personal-access-token-3ad0c5","errorCode":null,"errorMessage":"Invalid personal access token","messagePattern":"Invalid personal access token","errorType":"exception","errorClass":"GitlabServerException","httpStatus":401,"severity":"critical","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java","lineNumber":222,"sourceCode":"    checkResponseIsSuccessful(response, \"GitLab Merge Request did not happen, please check your configuration\");\n  }\n\n  protected static void checkResponseIsSuccessful(Response response, String errorMessage) throws IOException {\n    if (!response.isSuccessful()) {\n      String body = response.body().string();\n      LOG.error(\"Gitlab API call to [{}] failed with {} http code. gitlab response content : [{}]\", response.request().url(), response.code(), body);\n      if (isTokenRevoked(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token was revoked\");\n      } else if (isTokenExpired(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token is expired\");\n      } else if (isInsufficientScope(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token has insufficient scope\");\n      } else if (response.code() == HTTP_FORBIDDEN) {\n        throw new GitlabServerException(response.code(), \"Forbidden access to GitLab. Verify your token's permissions and IP restrictions.\");\n      } else if (response.code() == HTTP_TOO_MANY_REQUESTS) {\n        throw new GitlabServerException(response.code(), \"GitLab API rate limit exceeded. Try again later.\");\n      } else if (response.code() == HTTP_UNAUTHORIZED) {\n        throw new GitlabServerException(response.code(), \"Invalid personal access token\");\n      } else if (response.isRedirect()) {\n        throw new GitlabServerException(response.code(), \"Request was redirected, please provide the correct URL\");\n      } else {\n        throw new GitlabServerException(response.code(), errorMessage);\n      }\n    }\n  }\n\n  private static boolean isTokenRevoked(Response response, String body) {\n    if (response.code() == HTTP_UNAUTHORIZED) {\n      try {\n        Optional<GsonError> gitlabError = GsonError.parseOne(body);\n        return gitlabError.map(GsonError::getErrorDescription).map(description -> description.contains(\"Token was revoked\")).orElse(false);\n      } catch (JsonParseException e) {\n        // nothing to do\n      }\n    }\n    return false;","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java#L204-L240","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Regenerate the PAT in GitLab and paste it again carefully (no leading/trailing spaces) into SonarQube's GitLab integration settings.","Verify the token works against the configured URL: curl -H 'PRIVATE-TOKEN: <token>' <gitlab-url>/api/v4/user.","Ensure the GitLab URL in SonarQube matches the instance the token was created on.","Use a personal access token (not a deploy token or runner token) with 'api' and 'read_user' scopes."],"exampleFix":"// before (truncated token)\ntoken = \"glpat-AbC123\";\n// after (full token)\ntoken = \"glpat-AbC123fullTokenValueFromGitLab\";","handlingStrategy":"validation","validationCode":"// Validate the token before saving it in SonarQube settings\nResponse r = call(\"GET\", gitlabUrl + \"/api/v4/user\", token.trim());\nif (r.code() == 401) throw new IllegalArgumentException(\"Personal access token is invalid for \" + gitlabUrl + \" — regenerate it and paste without whitespace\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Trim tokens on input and paste the full token value.","Ensure the token is a PAT from the same GitLab instance as the configured URL.","Use SonarQube's 'Test configuration' before saving credentials."],"tags":["gitlab","authentication","token","http-401"],"backgroundTag":"missing-credentials","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"}