SonarSource/sonarqube · error · IllegalStateException

No self link found for Bitbucket Server repository

Error message

No self link found for Bitbucket Server repository '%s'

What it means

SonarQube throws this when resolving a live project binding for a Bitbucket Server repository whose REST response lacks a 'self' href link. The self link is required to build the canonical repository URL stored in the binding. It indicates the Bitbucket Server instance returned a repository payload without the expected links.

Solutions

  1. Verify the repository exists on the Bitbucket Server instance and that almRepo/almSlug match it exactly
  2. Check the Bitbucket Server REST response for the repo (GET /rest/api/1.0/projects/{project}/repos/{slug}) and confirm links.self is present
  3. Upgrade or repair the Bitbucket Server instance if links are missing from responses
  4. Re-create the ALM setting with the correct server URL and re-bind the project

Example fix

// before
{ "almRepo": "repo", "almSlug": "wrong-slug" }
// after
{ "almRepo": "repo", "almSlug": "correct-slug" }
Defensive patterns

Strategy: try-catch

Validate before calling

// Before binding, verify the repo's self link via the Bitbucket Server API:
const res = await fetch(`${serverUrl}/rest/api/1.0/projects/${proj}/repos/${slug}`);
const repo = await res.json();
if (!repo.links?.self?.find(l => l.href)) {
  throw new Error(`Bitbucket Server repo ${slug} exposes no self link; fix repo/SLUG or server version before binding`);
}

Type guard

function hasSelfHref(repo) {
  return typeof repo?.links?.self?.[0]?.href === 'string' && repo.links.self[0].href.length > 0;
}

Try / catch

try {
  resolveLive(almSetting, projectAlmSetting);
} catch (IllegalStateException e) {
  if (e.getMessage().contains("No self link found for Bitbucket Server repository")) {
    log.error("Check almRepo/almSlug and Bitbucket Server version; links.self missing in REST response", e);
    // surface actionable guidance or skip this binding
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling the project binding resolution flow (resolveLive -> resolveBitbucketServer) when bitbucketServerRestClient.getRepo() returns a Repository whose getSelfHref() is null — e.g. the configured almRepo/almSlug point at a repo whose links object is missing or the Bitbucket Server version returns a nonstandard links payload.

Common situations: Misconfigured almRepo/almSlug values pointing at an unexpected repo; older or customized Bitbucket Server versions omitting links.self; proxy or plugin modifying the REST response.

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/92712a3a3d9442d7. Report an issue: GitHub.

Appendix: source

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

    String url = requireNonNull(almSetting.getUrl(), URL_CANNOT_BE_NULL);
    String almSlug = requireNonNull(projectAlmSetting.getAlmSlug(), ALM_SLUG_CANNOT_BE_NULL);
    String almRepo = requireNonNull(projectAlmSetting.getAlmRepo(), ALM_REPO_CANNOT_BE_NULL);
    GsonAzureRepo repository = azureDevOpsHttpClient.getRepo(url, pat, almSlug, almRepo);
    String safeAlmRepo = sanitizeForLog(almRepo);
    String repoUrl = requireNonNull(repository.getWebUrl(), format("Azure DevOps returned no web URL for repository '%s'", safeAlmRepo));
    String repoId = requireNonNull(repository.getId(), format("Azure DevOps returned no id for repository '%s'", safeAlmRepo));
    return new LiveResolution(repoUrl, repoId);
  }

  private LiveResolution resolveBitbucketServer(AlmSettingDto almSetting, ProjectAlmSettingDto projectAlmSetting) {
    String pat = requireNonNull(almSetting.getDecryptedPersonalAccessToken(encryption), PAT_CANNOT_BE_NULL);
    String serverUrl = requireNonNull(almSetting.getUrl(), URL_CANNOT_BE_NULL);
    String almRepo = requireNonNull(projectAlmSetting.getAlmRepo(), ALM_REPO_CANNOT_BE_NULL);
    String almSlug = requireNonNull(projectAlmSetting.getAlmSlug(), ALM_SLUG_CANNOT_BE_NULL);
    org.sonar.alm.client.bitbucketserver.Repository repository = bitbucketServerRestClient.getRepo(serverUrl, pat, almRepo, almSlug);
    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();

View on GitHub (pinned to 184c821202)