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
- Confirm the workspace and almRepo values resolve to an existing Bitbucket Cloud repository
- Check GET /2.0/repositories/{workspace}/{repo} returns links.html.href
- Regenerate the OAuth client credentials/refresh the access token if responses are degraded
- 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
- Verify workspace and repository slugs are exact before binding
- Use an OAuth token with repository read scope for the correct workspace
- Confirm GET /2.0/repositories/{workspace}/{repo} returns links.html.href in your environment (watch proxies)
- Re-check bindings after repo renames or workspace migrations
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
- No self link found for Bitbucket Server repository
- No uuid found for Bitbucket Cloud repository
- Cannot mint a GitLab access token: project
- Cannot mint a GitLab access token: project
- e.getMessage()
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)