SonarSource/sonarqube · error · AzureDevopsServerException

Invalid personal access token

Error message

Invalid personal access token

What it means

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.

Source

Thrown at server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsHttpClient.java:237

      throw new IllegalArgumentException(
        String.format(UNABLE_TO_CONTACT_AZURE_SERVER_MESSAGE_FORMAT, UNABLE_TO_CONTACT_AZURE_SERVER, request.url(), e.getMessage()),
        e);
    }
  }

  protected static Request prepareRequestWithToken(String token, String method, HttpUrl url, @Nullable RequestBody body) {
    return new Request.Builder()
      .method(method, body)
      .url(url)
      .addHeader("Authorization", encodeToken("accessToken:" + token))
      .build();
  }

  protected static void checkResponseIsSuccessful(Response response) throws IOException {
    if (!response.isSuccessful()) {
      if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
        LOG.error("{} for request [{}]: Invalid personal access token", UNABLE_TO_CONTACT_AZURE_SERVER, response.request().url());
        throw new AzureDevopsServerException(response.code(), "Invalid personal access token");
      }

      if (response.code() == HttpURLConnection.HTTP_NOT_FOUND) {
        LOG.error("{} for request [{}]: URL Not Found", UNABLE_TO_CONTACT_AZURE_SERVER, response.request().url());
        throw new AzureDevopsServerException(response.code(), "Invalid Azure URL");
      }

      ResponseBody responseBody = response.body();
      String body = responseBody.string();
      String errorMessage = generateErrorMessage(body);
      LOG.error("Azure API call to [{}] failed with {} http code. Azure response content : [{}]", response.request().url(), response.code(), body);
      throw new AzureDevopsServerException(response.code(), errorMessage);
    }
  }

  protected static String generateErrorMessage(String body) {
    GsonAzureError gsonAzureError = null;
    try {

View on GitHub (pinned to 184c821202)

Solutions

  1. 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.
  2. Confirm the PAT's organization/expiration and that it has not been revoked.
  3. Ensure the Azure DevOps Server URL matches the collection/organization the PAT belongs to.
  4. Re-run the Azure DevOps integration validation after updating the token.

Example fix

// before
pat=expired-token-...

// after
pat=<newly generated PAT with Code>Read scope>
Defensive patterns

Strategy: validation

Validate before calling

// validate PAT before configuring
Request req = new Request.Builder()
  .url(azureUrl + "/_apis/projects?api-version=5.0")
  .header("Authorization", Credentials.basic("", pat))
  .build();
try (Response r = client.newCall(req).execute()) {
  if (r.code() == 401) throw new IllegalArgumentException("PAT invalid or expired");
}

Try / catch

try {
  client.importAzureRepos(url, pat);
} catch (AzureDevopsServerException e) {
  if (e.getCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
    throw new ConfigurationException("Personal access token invalid/expired — regenerate it in Azure DevOps", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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