SonarSource/sonarqube · error · IllegalStateException

No html link found for Bitbucket Cloud repository

Error message

No html link found for Bitbucket Cloud repository '%s'

What it means

SonarQube throws this when resolving a Bitbucket Cloud project binding and the repository payload returned by the Bitbucket Cloud API has no 'html' link. The html href is the canonical web URL required to store in the binding. It signals the API response lacks the expected links.html.href field.

Solutions

  1. Confirm the workspace and almRepo values resolve to an existing Bitbucket Cloud repository
  2. Check GET /2.0/repositories/{workspace}/{repo} returns links.html.href
  3. Regenerate the OAuth client credentials/refresh the access token if responses are degraded
  4. Re-bind the project with corrected workspace/repo settings

Example fix

// before
workspace: "acme", almRepo: "rep"  // typo
// after
workspace: "acme", almRepo: "repo"
Defensive patterns

Strategy: validation

Validate before calling

const res = await fetch(`https://api.bitbucket.org/2.0/repositories/${workspace}/${repo}`, { headers: { Authorization: `Bearer ${token}` } });
const data = await res.json();
if (!data.links?.html?.href) {
  throw new Error(`Repo ${workspace}/${repo} has no html link; check workspace/slug and token scope`);
}

Type guard

function hasHtmlHref(repo) {
  return typeof repo?.links?.html?.href === 'string' && repo.links.html.href.length > 0;
}

Prevention

When it happens

Trigger: resolveLive -> resolveBitbucketCloud calls bitbucketCloudRestClient.getRepoWithAccessToken(accessToken, workspace, almRepo) and the returned Repository's getHtmlHref() is null — e.g. wrong workspace/repo slug, or a response shape without links.html.

Common situations: Typo in workspace or repository name; repo moved/renamed; API returning 4xx wrapped as empty links; custom API gateway stripping links fields.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/ProjectBindingsServiceServerImpl.java:332

    String url = repository.getSelfHref();
    if (url == null) {
      throw new IllegalStateException(format("No self link found for Bitbucket Server repository '%s'", sanitizeForLog(almSlug)));
    }
    return new LiveResolution(url, String.valueOf(repository.getId()));
  }

  private LiveResolution resolveBitbucketCloud(AlmSettingDto almSetting, ProjectAlmSettingDto projectAlmSetting, Map<String, String> tokenCache) {
    String clientId = requireNonNull(almSetting.getClientId(), CLIENT_ID_CANNOT_BE_NULL);
    String clientSecret = requireNonNull(almSetting.getDecryptedClientSecret(encryption), CLIENT_SECRET_CANNOT_BE_NULL);
    String workspace = requireNonNull(almSetting.getAppId(), WORKSPACE_CANNOT_BE_NULL);
    String almRepo = requireNonNull(projectAlmSetting.getAlmRepo(), ALM_REPO_CANNOT_BE_NULL);
    // The OAuth token is workspace-wide, not per-repository: reuse it across every binding of this ALM setting
    // resolved within the same request instead of minting a fresh one per binding.
    String accessToken = tokenCache.computeIfAbsent(almSetting.getUuid(), uuid -> bitbucketCloudRestClient.createAccessToken(clientId, clientSecret));
    org.sonar.alm.client.bitbucket.bitbucketcloud.Repository repository = bitbucketCloudRestClient.getRepoWithAccessToken(accessToken, workspace, almRepo);
    String url = repository.getHtmlHref();
    if (url == null) {
      throw new IllegalStateException(format("No html link found for Bitbucket Cloud repository '%s'", sanitizeForLog(almRepo)));
    }
    String repoId = repository.getUuid();
    if (repoId == null) {
      throw new IllegalStateException(format("No uuid found for Bitbucket Cloud repository '%s'", sanitizeForLog(almRepo)));
    }
    return new LiveResolution(url, repoId);
  }

  /**
   * GitHub's installation-token "repositories" scoping parameter expects the bare repository name
   * (no {@code owner/} prefix), unlike {@code almRepo} which is stored as {@code owner/repo}.
   */
  private static String bareRepositoryName(String almRepo) {
    int lastSlash = almRepo.lastIndexOf('/');
    return lastSlash < 0 ? almRepo : almRepo.substring(lastSlash + 1);
  }

  /**

View on GitHub (pinned to 184c821202)