{"record":{"id":"b58779efb3591fa4","repo":"SonarSource/sonarqube","slug":"failed-to-resolve-url-repoid-for-devops-platform-b","errorCode":null,"errorMessage":"Failed to resolve url/repoId for DevOps Platform binding '{}': {} ({})","messagePattern":"Failed to resolve url/repoId for DevOps Platform binding '(.+?)': (.+?) \\((.+?)\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/ProjectBindingsServiceServerImpl.java","lineNumber":258,"sourceCode":"      dbSession.rollback();\n    }\n  }\n\n  private LiveResolution resolveLive(AlmSettingDto almSetting, ProjectAlmSettingDto projectAlmSetting, Map<String, String> bitbucketCloudTokenCache) {\n    try {\n      return switch (almSetting.getAlm()) {\n        case GITHUB -> resolveGithub(almSetting, projectAlmSetting);\n        case GITLAB -> resolveGitlab(almSetting, projectAlmSetting);\n        case AZURE_DEVOPS -> resolveAzure(almSetting, projectAlmSetting);\n        case BITBUCKET -> resolveBitbucketServer(almSetting, projectAlmSetting);\n        case BITBUCKET_CLOUD -> resolveBitbucketCloud(almSetting, projectAlmSetting, bitbucketCloudTokenCache);\n      };\n    } catch (Exception e) {\n      // Never logs \"e\" directly: exceptions thrown by the ALM REST clients themselves (not just this class) can\n      // embed raw, externally-influenced data (an ALM-side error response body, a repository identifier) in their\n      // message chain — logging the throwable as-is would re-open the same CRLF log-injection issue that\n      // sanitizeForLog exists to close, just one layer down, in code this class doesn't control.\n      LOG.warn(\"Failed to resolve url/repoId for DevOps Platform binding '{}': {} ({})\", projectAlmSetting.getUuid(),\n        sanitizeForLog(String.valueOf(e.getMessage())), e.getClass().getSimpleName());\n      return new LiveResolution(\"\", \"\");\n    }\n  }\n\n  private LiveResolution resolveGithub(AlmSettingDto almSetting, ProjectAlmSettingDto projectAlmSetting) {\n    String almRepo = requireNonNull(projectAlmSetting.getAlmRepo(), ALM_REPO_CANNOT_BE_NULL);\n    String safeAlmRepo = sanitizeForLog(almRepo);\n    String url = requireNonNull(almSetting.getUrl(), URL_CANNOT_BE_NULL);\n    GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSetting);\n    long installationId = githubApplicationClient.getInstallationId(githubAppConfiguration, almRepo)\n      .orElseThrow(() -> new IllegalStateException(format(\"GitHub App is not installed on repository '%s'\", safeAlmRepo)));\n    AppInstallationToken accessToken = githubApplicationClient.createAppInstallationToken(githubAppConfiguration, installationId, bareRepositoryName(almRepo))\n      .orElseThrow(() -> new IllegalStateException(format(\"Failed to create a GitHub App installation token for repository '%s'\", safeAlmRepo)));\n    GithubApplicationClient.Repository repository = githubApplicationClient.getRepository(url, accessToken, almRepo)\n      .orElseThrow(() -> new IllegalStateException(format(\"Repository '%s' not found on GitHub\", safeAlmRepo)));\n    String repoUrl = requireNonNull(repository.getUrl(), format(\"GitHub returned no url for repository '%s'\", safeAlmRepo));\n    return new LiveResolution(repoUrl, Long.toString(repository.getId()));","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/ProjectBindingsServiceServerImpl.java#L240-L276","documentation":"This warning is logged by ProjectBindingsServiceServerImpl.resolveLive when live resolution of a binding's repository URL and repoId from the ALM REST API fails. The method returns an empty LiveResolution so the search continues; only the sanitized message and exception class name are logged because ALM client exceptions can embed externally-influenced data in their message chains (log-injection risk).","triggerScenarios":"api/project_bindings/search (or resolveUrlAndRepoId) where the ALM client call for GitHub/GitLab/Azure/Bitbucket fails: unreachable ALM host, invalid credentials, wrong repository identifiers, rate limits, or malformed ALM responses.","commonSituations":"Firewall change blocking egress to the ALM; expired PAT/app credential; repository renamed; ALM returning 5xx or non-JSON bodies; self-signed certificates failing TLS validation.","solutions":["Check the logged exception class/message to identify the failing ALM and error type (IO vs HTTP vs parse)","Validate the ALM setting's URL and credentials from the SonarQube host (curl the ALM API with the configured token)","Re-bind the affected project if repository identifiers changed (api/alm_settings/set_binding)","Retry the search — resolution is transient-cached; once the ALM is reachable the url/repoId are persisted via the persist path"],"exampleFix":"// before: unreachable ALM\nFailed to resolve url/repoId ... (SocketTimeoutException)\n// after fixing connectivity\ncurl https://alm.example.com/api/v3/rate_limit -H 'Authorization: token <PAT>' → 200, bindings resolve","handlingStrategy":"retry","validationCode":"// Pre-flight ALM reachability before heavy resolution\nboolean reachable = pingAlm(almSetting.getUrl()); // HTTP probe with short timeout\nif (!reachable) throw new IllegalStateException(\"ALM unreachable: \" + almSetting.getUrl());","typeGuard":null,"tryCatchPattern":"// Same resilient pattern the code uses: fall back to empty resolution\ntry { return resolveFromAlm(almSetting, binding); }\ncatch (Exception e) { log.warn(\"resolve failed: {} ({})\", sanitize(e.getMessage()), e.getClass().getSimpleName()); return new LiveResolution(\"\", \"\"); }","preventionTips":["Monitor egress connectivity and TLS trust to ALM hosts from the SonarQube host","Rotate ALM credentials before expiry","Handle ALM rate limits with backoff on the client side","Treat empty LiveResolution as a cache miss and retry later"],"tags":["alm-integration","network","upstream-api","resilience","logging"],"backgroundTag":"upstream-api-error","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"}