{"record":{"id":"7b9a87dde137370d","repo":"SonarSource/sonarqube","slug":"your-gitlab-token-is-expired","errorCode":null,"errorMessage":"Your GitLab token is expired","messagePattern":"Your GitLab token is expired","errorType":"exception","errorClass":"GitlabServerException","httpStatus":null,"severity":"critical","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java","lineNumber":214,"sourceCode":"    try {\n      return URLEncoder.encode(value, UTF_8.toString());\n    } catch (UnsupportedEncodingException ex) {\n      throw new IllegalStateException(ex.getCause());\n    }\n  }\n\n  protected static void checkResponseIsSuccessful(Response response) throws IOException {\n    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) {","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java#L196-L232","documentation":"checkResponseIsSuccessful maps a failing GitLab response whose body indicates an expired token to GitlabServerException 'Your GitLab token is expired'. GitLab personal access tokens can carry an expiry date; once passed, all API calls fail with 401 and this error is surfaced so the operator knows the token must be renewed, not that permissions are wrong.","triggerScenarios":"Any GitLab API call via checkResponseIsSuccessful (checkProjectAccess, checkToken, getPersonalAccessTokenInfo, checkWritePermission, createProjectAccessToken) returns 401 with a body matching the expired-token signature at GitlabApplicationClient.java:214.","commonSituations":"PAT reached its expiration date configured in GitLab; GitLab instance enforces token expiry policies; a long-lived SonarQube binding outlived the token's validity window.","solutions":["Renew the personal access token in GitLab (User Settings > Access Tokens > extend expiry or create a new one).","Update the new token in SonarQube's GitLab integration settings and re-test the configuration.","Consider GitLab's non-expiring token option (if instance policy allows) or a service-account token with a long expiry plus rotation reminders.","Use GitLab project access tokens with automated rotation if the instance supports it."],"exampleFix":"// GitLab: PAT expiring\nexpires_at: 2024-01-01\n// after: extend or recreate\nexpires_at: 2027-01-01  (or 'no expiration' where policy permits)","handlingStrategy":"validation","validationCode":"// Check token expiry before use\nJsonObject info = getJson(gitlabUrl + \"/api/v4/personal_access_tokens/self\", token);\nString expiresAt = info.get(\"expires_at\").isJsonNull() ? null : info.get(\"expires_at\").getAsString();\nif (expiresAt != null && LocalDate.parse(expiresAt).isBefore(LocalDate.now().plusDays(7))) {\n  log.warn(\"GitLab PAT expires on {} — renew it soon\", expiresAt);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Set calendar reminders before the PAT expiry date.","Prefer no-expiry or long-expiry tokens where instance policy allows, with scheduled rotation.","Call /personal_access_tokens/self periodically to surface approaching expiry."],"tags":["gitlab","authentication","token","http-401","expiry"],"backgroundTag":"jwt-token-expired","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}