SonarSource/sonarqube · error · IllegalArgumentException

Error returned by Bitbucket Cloud: %s

Error message

Error returned by Bitbucket Cloud: %s

What it means

In validateAccessToken(), the token endpoint returned a non-success HTTP status whose body contained an 'error' string that is neither 'invalid_grant' nor 'unauthorized_client', but a JSON 'error_description' (parsedErrorMsg) was present. The client then throws IllegalArgumentException('Error returned by Bitbucket Cloud: ' + parsedErrorMsg), surfacing Bitbucket's own error description verbatim to the SonarQube admin. It is a pass-through wrapper around whatever OAuth error Bitbucket chose to report.

Source

Thrown at server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java:143

  private Token validateAccessToken(String clientId, String clientSecret) {
    Request request = createAccessTokenRequest(clientId, clientSecret);
    try (Response response = client.newCall(request).execute()) {
      if (response.isSuccessful()) {
        return buildGson().fromJson(response.body().charStream(), Token.class);
      }

      ErrorDetails errorMsg = getTokenError(response.body(), response.message());
      if (errorMsg.body != null) {
        LOG.atInfo().log(() -> String.format(BBC_FAIL_WITH_RESPONSE, response.request().url(), response.code(), errorMsg.body));
        switch (errorMsg.body) {
          case "invalid_grant":
            throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS + ": " + OAUTH_CONSUMER_NOT_PRIVATE);
          case "unauthorized_client":
            throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS + ": " + UNAUTHORIZED_CLIENT);
          default:
            if (errorMsg.parsedErrorMsg != null) {
              throw new IllegalArgumentException(ERROR_BBC_SERVERS + ": " + errorMsg.parsedErrorMsg);
            } else {
              throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS);
            }
        }
      } else {
        LOG.atInfo().log(() -> String.format(BBC_FAIL_WITH_RESPONSE, response.request().url(), response.code(), response.message()));
      }
      throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS);

    } catch (IOException e) {
      LOG.info(String.format(BBC_FAIL_WITH_ERROR, request.url(), e.getMessage()));
      throw new IllegalArgumentException(UNABLE_TO_CONTACT_BBC_SERVERS, e);
    }
  }

  public RepositoryList searchRepos(String encodedApiTokenCredentials, String workspace, @Nullable String repoName, Integer page, Integer pageSize) {
    String filterQuery = String.format("q=name~\"%s\"", repoName != null ? repoName : "");
    HttpUrl url = buildUrl(String.format("/repositories/%s?%s&page=%s&pagelen=%s", workspace, filterQuery, page, pageSize));

View on GitHub (pinned to 184c821202)

Solutions

  1. Read the 'Error returned by Bitbucket Cloud: <description>' suffix — it is Bitbucket's own error_description; address the OAuth issue it names
  2. Confirm the token request is exactly grant_type=client_credentials with the consumer's Key/Secret as HTTP Basic (no extra or missing parameters)
  3. Check the Bitbucket consumer is a private consumer in the correct workspace with Account/Repository/PullRequest read scopes
  4. Check Bitbucket Cloud status for ongoing incidents and retry if the description suggests a transient/server-side condition
  5. Inspect the server INFO log line 'Bitbucket Cloud API call to [...] failed with <code> http code' for the exact HTTP status

Example fix

// before: body {"error":"invalid_request","error_description":"grant_type not supported"}
// (thrown from validateAccessToken default branch)
// after: send the exact expected form
RequestBody body = new FormBody.Builder().add("grant_type", "client_credentials").build();
Request req = new Request.Builder().url(tokenUrl).header("Authorization", Credentials.basic(clientId, clientSecret)).post(body).build();
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight the exact token request and inspect the OAuth error_description
// curl -s -X POST -u "$KEY":"$SECRET" -d grant_type=client_credentials \
//   https://bitbucket.org/site/oauth2/access_token
// The JSON 'error_description' field is what will appear after 'Error returned by Bitbucket Cloud: '
if (tokenResponse.contains("\"error_description\"")) {
  String desc = extractJsonField(tokenResponse, "error_description");
  throw new ConfigurationException("Bitbucket rejected the grant: " + desc);
}

Try / catch

try {
  client.validate(clientId, clientSecret, workspace);
} catch (IllegalArgumentException e) {
  if (e.getMessage().startsWith("Error returned by Bitbucket Cloud:")) {
    // Bitbucket's own error_description follows — surface it verbatim to the admin
    log.error("Bitbucket OAuth error: {}", e.getMessage());
  }
  throw e;
}

Prevention

When it happens

Trigger: POST to https://bitbucket.org/site/oauth2/access_token with grant_type=client_credentials and Basic credentials returns a non-2xx response whose JSON body parses to TokenError with a non-null errorDescription and an 'error' value outside {invalid_grant, unauthorized_client} — e.g. 'invalid_request' (missing/malformed parameters), 'invalid_client' variants, or other OAuth errors with descriptions.

Common situations: Proxies or gateways rewriting the token request causing invalid_request; consumer misconfigured so Bitbucket rejects the grant with a descriptive message; temporarily returned upstream descriptions during Bitbucket incidents; custom reverse-proxy in front of bitbucket.org altering the request flow.

Related errors


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