{"record":{"id":"ffee361f5e79fa40","repo":"SonarSource/sonarqube","slug":"invalid-personal-access-token","errorCode":null,"errorMessage":"Invalid personal access token","messagePattern":"Invalid personal access token","errorType":"http","errorClass":"AzureDevopsServerException","httpStatus":401,"severity":"error","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsHttpClient.java","lineNumber":237,"sourceCode":"      throw new IllegalArgumentException(\n        String.format(UNABLE_TO_CONTACT_AZURE_SERVER_MESSAGE_FORMAT, UNABLE_TO_CONTACT_AZURE_SERVER, request.url(), e.getMessage()),\n        e);\n    }\n  }\n\n  protected static Request prepareRequestWithToken(String token, String method, HttpUrl url, @Nullable RequestBody body) {\n    return new Request.Builder()\n      .method(method, body)\n      .url(url)\n      .addHeader(\"Authorization\", encodeToken(\"accessToken:\" + token))\n      .build();\n  }\n\n  protected static void checkResponseIsSuccessful(Response response) throws IOException {\n    if (!response.isSuccessful()) {\n      if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {\n        LOG.error(\"{} for request [{}]: Invalid personal access token\", UNABLE_TO_CONTACT_AZURE_SERVER, response.request().url());\n        throw new AzureDevopsServerException(response.code(), \"Invalid personal access token\");\n      }\n\n      if (response.code() == HttpURLConnection.HTTP_NOT_FOUND) {\n        LOG.error(\"{} for request [{}]: URL Not Found\", UNABLE_TO_CONTACT_AZURE_SERVER, response.request().url());\n        throw new AzureDevopsServerException(response.code(), \"Invalid Azure URL\");\n      }\n\n      ResponseBody responseBody = response.body();\n      String body = responseBody.string();\n      String errorMessage = generateErrorMessage(body);\n      LOG.error(\"Azure API call to [{}] failed with {} http code. Azure response content : [{}]\", response.request().url(), response.code(), body);\n      throw new AzureDevopsServerException(response.code(), errorMessage);\n    }\n  }\n\n  protected static String generateErrorMessage(String body) {\n    GsonAzureError gsonAzureError = null;\n    try {","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsHttpClient.java#L219-L255","documentation":"checkResponseIsSuccessful maps an Azure DevOps HTTP 401 response to AzureDevopsServerException(code=401, 'Invalid personal access token'). The configured token was rejected by the server as authentication failed while performing an Azure DevOps API call.","triggerScenarios":"doGet/doCall receives a response with code 401 Unauthorized — the PAT is wrong, expired, revoked, lacks the required scopes, or was generated on a different organization/collection than the configured URL.","commonSituations":"PAT expired (default 30/90-day lifetimes), token rotated without updating SonarQube settings, PAT scoped to one organization but used against another, or a fine-grained token missing 'Code > Read' scope.","solutions":["Generate a new PAT in Azure DevOps with the required scopes (e.g. Code Read, or full access for validation) and update it in SonarQube devops integration settings.","Confirm the PAT's organization/expiration and that it has not been revoked.","Ensure the Azure DevOps Server URL matches the collection/organization the PAT belongs to.","Re-run the Azure DevOps integration validation after updating the token."],"exampleFix":"// before\npat=expired-token-...\n\n// after\npat=<newly generated PAT with Code>Read scope>","handlingStrategy":"validation","validationCode":"// validate PAT before configuring\nRequest req = new Request.Builder()\n  .url(azureUrl + \"/_apis/projects?api-version=5.0\")\n  .header(\"Authorization\", Credentials.basic(\"\", pat))\n  .build();\ntry (Response r = client.newCall(req).execute()) {\n  if (r.code() == 401) throw new IllegalArgumentException(\"PAT invalid or expired\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  client.importAzureRepos(url, pat);\n} catch (AzureDevopsServerException e) {\n  if (e.getCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {\n    throw new ConfigurationException(\"Personal access token invalid/expired — regenerate it in Azure DevOps\", e);\n  }\n  throw e;\n}","preventionTips":["Set PAT expiry reminders; SonarQube does not rotate tokens automatically.","Use the minimum required scopes (e.g. Code > Read) but ensure they cover the integration.","Ensure the PAT belongs to the same organization/collection as the configured URL.","Re-validate the integration immediately after rotating a PAT."],"tags":["java","azure-devops","authentication","pat"],"backgroundTag":"authentication-required","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"}