{"record":{"id":"eabc988eafe6b60e","repo":"SonarSource/sonarqube","slug":"error-returned-by-bitbucket-cloud-s-http-s","errorCode":null,"errorMessage":"Error returned by Bitbucket Cloud: %s [HTTP %s] | Unable to contact Bitbucket Cloud servers [HTTP %s]","messagePattern":"Error returned by Bitbucket Cloud: (.+?) \\[HTTP (.+?)\\] \\| Unable to contact Bitbucket Cloud servers \\[HTTP (.+?)\\]","errorType":"http","errorClass":"BitbucketCloudException","httpStatus":null,"severity":"error","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java","lineNumber":247,"sourceCode":"      }\n      return handler.apply(response);\n    } catch (IOException e) {\n      LOG.info(ERROR_BBC_SERVERS + \": {}\", e.getMessage());\n      throw new IllegalStateException(ERROR_BBC_SERVERS, e);\n    }\n  }\n\n  private static void handleError(Response response) throws IOException {\n    ErrorDetails error = getError(response.body(), response.message());\n    int statusCode = response.code();\n    LOG.atInfo().log(() -> String.format(BBC_FAIL_WITH_RESPONSE, response.request().url(), statusCode, error.body));\n    String errorMessage;\n    if (error.parsedErrorMsg != null) {\n      errorMessage = ERROR_BBC_SERVERS + \": \" + error.parsedErrorMsg + \" [HTTP \" + statusCode + \"]\";\n    } else {\n      errorMessage = UNABLE_TO_CONTACT_BBC_SERVERS + \" [HTTP \" + statusCode + \"]\";\n    }\n    throw new BitbucketCloudException(errorMessage, statusCode);\n  }\n\n  private static ErrorDetails getError(@Nullable ResponseBody body, @Nullable String fallbackMessage) throws IOException {\n    return getErrorDetails(body, fallbackMessage, s -> {\n      Error gsonError = buildGson().fromJson(s, Error.class);\n      if (gsonError != null && gsonError.errorMsg != null && gsonError.errorMsg.message != null) {\n        return gsonError.errorMsg.message;\n      }\n      return null;\n    });\n  }\n\n  private static ErrorDetails getTokenError(@Nullable ResponseBody body, @Nullable String fallbackMessage) throws IOException {\n    if (body == null) {\n      return new ErrorDetails(fallbackMessage, null);\n    }\n    String bodyStr = body.string();\n    if (bodyStr.isBlank()) {","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/bitbucket/bitbucketcloud/BitbucketCloudRestClient.java#L229-L265","documentation":"handleError builds the failure message for any non-2xx Bitbucket Cloud API response and throws BitbucketCloudException(errorMessage, statusCode). If the response body was JSON with a parseable 'error.message', the message is 'Error returned by Bitbucket Cloud: <parsed msg> [HTTP <code>]'; otherwise it falls back to 'Unable to contact Bitbucket Cloud servers [HTTP <code>]'. The status code (401, 403, 404, 429, 5xx...) is the real signal about what went wrong.","triggerScenarios":"Thrown by handleError, invoked from doCall (callers: createAccessToken, doGet, doGetWithApiToken) whenever response.isSuccessful() is false on calls to api.bitbucket.org/2.0/... : 401 invalid credentials, 403 missing permission, 404 unknown workspace/repo, 429 rate limited, 5xx server errors; or when the error body is not JSON / lacks error.errorMsg.message so no parsed message is available.","commonSituations":"Expired or revoked access/API token (401); OAuth token lacking the 'pullrequest' scope; querying a workspace slug that does not exist or is misspelled (404); hitting Bitbucket Cloud rate limits on polling integrations (429); transient 502/503 during Bitbucket incidents where the body is HTML so no parsed message is available; firewall returning a captive-portal HTML page.","solutions":["Parse the '[HTTP <code>]' suffix from the message and branch on it — the code is the authoritative cause.","401/403: regenerate the access token or API token and ensure the OAuth consumer/token has the required scopes (repository read, pull request read).","404: verify the workspace and repository slugs passed to getRepo/searchRepos match Bitbucket Cloud exactly.","429: add/exponential backoff in the caller and reduce polling frequency; honor the Retry-After header.","5xx or HTML-body responses: retry with backoff and check the Bitbucket Cloud status page; treat it as transient."],"exampleFix":"// before: treating every BitbucketCloudException the same\ntry { client.getRepo(cred, ws, slug); } catch (BitbucketCloudException e) { retry(); }\n// after: branch on the embedded status code\ntry { client.getRepo(cred, ws, slug); }\ncatch (BitbucketCloudException e) {\n  if (e.getHttpCode() == 404) { throw new IllegalArgumentException(\"Unknown repo \" + slug); }\n  if (e.getHttpCode() == 429 || e.getHttpCode() >= 500) { backoffAndRetry(); }\n  else { throw new IllegalStateException(\"Auth/permission problem: \" + e.getMessage(), e); }\n}","handlingStrategy":"type-guard","validationCode":"// Before calling the API, validate inputs that drive the URL and auth:\nstatic void precheck(String workspace, String slug, String token) {\n  if (workspace == null || !workspace.matches(\"[A-Za-z0-9][A-Za-z0-9_.-]*\"))\n    throw new IllegalArgumentException(\"Invalid workspace slug: prevents 404s\");\n  if (slug != null && !slug.matches(\"[A-Za-z0-9_.-]+\"))\n    throw new IllegalArgumentException(\"Invalid repo slug: prevents 404s\");\n  if (token == null || token.isBlank())\n    throw new IllegalArgumentException(\"Blank token: prevents 401s\");\n}","typeGuard":"// Narrow BitbucketCloudException by its embedded HTTP status before reacting:\nstatic boolean isAuthError(BitbucketCloudException e) {\n  return e.getHttpCode() == 401 || e.getHttpCode() == 403;\n}\nstatic boolean isNotFound(BitbucketCloudException e) { return e.getHttpCode() == 404; }\nstatic boolean isRateLimited(BitbucketCloudException e) { return e.getHttpCode() == 429; }\nstatic boolean isTransient(BitbucketCloudException e) {\n  return e.getHttpCode() == 429 || e.getHttpCode() >= 500;\n}","tryCatchPattern":"try {\n  return restClient.getRepo(cred, workspace, slug);\n} catch (BitbucketCloudException e) {\n  int code = e.getHttpCode();\n  if (code == 401 || code == 403) {\n    throw new ConfigurationException(\"Bitbucket token invalid or missing scopes (HTTP \" + code + \")\", e);\n  } else if (code == 404) {\n    throw new IllegalArgumentException(\"Workspace/repo not found: '\" + workspace + \"/\" + slug + \"'\");\n  } else if (code == 429 || code >= 500) {\n    throw new TransientApiException(\"Bitbucket Cloud temporarily failing (HTTP \" + code + \"), retry with backoff\", e);\n  }\n  throw e; // includes the '[HTTP n]' suffix and any parsed Bitbucket message\n}","preventionTips":["Always branch on getHttpCode() rather than the message text — the code is stable, the wording is not.","Keep tokens valid and scoped (repository + pull request read) to avoid 401/403.","Spell-check workspace/repository slugs; 404s are the most common avoidable cause.","Implement exponential backoff for 429/5xx and honor Retry-After headers to stay under rate limits.","Log the request URL alongside failures (mirroring the client's INFO logs) so failures are debuggable later."],"tags":["bitbucket-cloud","http-status","api-error","rate-limit","error-parsing"],"backgroundTag":"api-error-response","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"}