{"record":{"id":"92712a3a3d9442d7","repo":"SonarSource/sonarqube","slug":"no-self-link-found-for-bitbucket-server-repository","errorCode":null,"errorMessage":"No self link found for Bitbucket Server repository '%s'","messagePattern":"No self link found for Bitbucket Server repository '(.+?)'","errorType":"exception","errorClass":"IllegalStateException","httpStatus":500,"severity":"error","filePath":"server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/ProjectBindingsServiceServerImpl.java","lineNumber":316,"sourceCode":"    String url = requireNonNull(almSetting.getUrl(), URL_CANNOT_BE_NULL);\n    String almSlug = requireNonNull(projectAlmSetting.getAlmSlug(), ALM_SLUG_CANNOT_BE_NULL);\n    String almRepo = requireNonNull(projectAlmSetting.getAlmRepo(), ALM_REPO_CANNOT_BE_NULL);\n    GsonAzureRepo repository = azureDevOpsHttpClient.getRepo(url, pat, almSlug, almRepo);\n    String safeAlmRepo = sanitizeForLog(almRepo);\n    String repoUrl = requireNonNull(repository.getWebUrl(), format(\"Azure DevOps returned no web URL for repository '%s'\", safeAlmRepo));\n    String repoId = requireNonNull(repository.getId(), format(\"Azure DevOps returned no id for repository '%s'\", safeAlmRepo));\n    return new LiveResolution(repoUrl, repoId);\n  }\n\n  private LiveResolution resolveBitbucketServer(AlmSettingDto almSetting, ProjectAlmSettingDto projectAlmSetting) {\n    String pat = requireNonNull(almSetting.getDecryptedPersonalAccessToken(encryption), PAT_CANNOT_BE_NULL);\n    String serverUrl = requireNonNull(almSetting.getUrl(), URL_CANNOT_BE_NULL);\n    String almRepo = requireNonNull(projectAlmSetting.getAlmRepo(), ALM_REPO_CANNOT_BE_NULL);\n    String almSlug = requireNonNull(projectAlmSetting.getAlmSlug(), ALM_SLUG_CANNOT_BE_NULL);\n    org.sonar.alm.client.bitbucketserver.Repository repository = bitbucketServerRestClient.getRepo(serverUrl, pat, almRepo, almSlug);\n    String url = repository.getSelfHref();\n    if (url == null) {\n      throw new IllegalStateException(format(\"No self link found for Bitbucket Server repository '%s'\", sanitizeForLog(almSlug)));\n    }\n    return new LiveResolution(url, String.valueOf(repository.getId()));\n  }\n\n  private LiveResolution resolveBitbucketCloud(AlmSettingDto almSetting, ProjectAlmSettingDto projectAlmSetting, Map<String, String> tokenCache) {\n    String clientId = requireNonNull(almSetting.getClientId(), CLIENT_ID_CANNOT_BE_NULL);\n    String clientSecret = requireNonNull(almSetting.getDecryptedClientSecret(encryption), CLIENT_SECRET_CANNOT_BE_NULL);\n    String workspace = requireNonNull(almSetting.getAppId(), WORKSPACE_CANNOT_BE_NULL);\n    String almRepo = requireNonNull(projectAlmSetting.getAlmRepo(), ALM_REPO_CANNOT_BE_NULL);\n    // The OAuth token is workspace-wide, not per-repository: reuse it across every binding of this ALM setting\n    // resolved within the same request instead of minting a fresh one per binding.\n    String accessToken = tokenCache.computeIfAbsent(almSetting.getUuid(), uuid -> bitbucketCloudRestClient.createAccessToken(clientId, clientSecret));\n    org.sonar.alm.client.bitbucket.bitbucketcloud.Repository repository = bitbucketCloudRestClient.getRepoWithAccessToken(accessToken, workspace, almRepo);\n    String url = repository.getHtmlHref();\n    if (url == null) {\n      throw new IllegalStateException(format(\"No html link found for Bitbucket Cloud repository '%s'\", sanitizeForLog(almRepo)));\n    }\n    String repoId = repository.getUuid();","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/ProjectBindingsServiceServerImpl.java#L298-L334","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the repository exists on the Bitbucket Server instance and that almRepo/almSlug match it exactly","Check the Bitbucket Server REST response for the repo (GET /rest/api/1.0/projects/{project}/repos/{slug}) and confirm links.self is present","Upgrade or repair the Bitbucket Server instance if links are missing from responses","Re-create the ALM setting with the correct server URL and re-bind the project"],"exampleFix":"// before\n{ \"almRepo\": \"repo\", \"almSlug\": \"wrong-slug\" }\n// after\n{ \"almRepo\": \"repo\", \"almSlug\": \"correct-slug\" }","handlingStrategy":"try-catch","validationCode":"// Before binding, verify the repo's self link via the Bitbucket Server API:\nconst res = await fetch(`${serverUrl}/rest/api/1.0/projects/${proj}/repos/${slug}`);\nconst repo = await res.json();\nif (!repo.links?.self?.find(l => l.href)) {\n  throw new Error(`Bitbucket Server repo ${slug} exposes no self link; fix repo/SLUG or server version before binding`);\n}","typeGuard":"function hasSelfHref(repo) {\n  return typeof repo?.links?.self?.[0]?.href === 'string' && repo.links.self[0].href.length > 0;\n}","tryCatchPattern":"try {\n  resolveLive(almSetting, projectAlmSetting);\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"No self link found for Bitbucket Server repository\")) {\n    log.error(\"Check almRepo/almSlug and Bitbucket Server version; links.self missing in REST response\", e);\n    // surface actionable guidance or skip this binding\n  } else { throw e; }\n}","preventionTips":["Validate almRepo/almSlug against the Bitbucket Server API before creating the binding","Pin a Bitbucket Server version known to return links.self for repo payloads","Test one binding resolution after any Bitbucket Server upgrade","Log the raw REST response when resolution fails to diagnose missing links"],"tags":["bitbucket-server","alm-binding","rest-client","missing-link"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}