{"record":{"id":"070bd6bf4e011639","repo":"SonarSource/sonarqube","slug":"failed-to-persist-resolved-url-repoid-for-devops-p","errorCode":null,"errorMessage":"Failed to persist resolved url/repoId for DevOps Platform binding '{}': {} ({})","messagePattern":"Failed to persist resolved 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":235,"sourceCode":"      return new LiveResolution(storedUrl, storedRepoId);\n    }\n\n    LiveResolution resolved = resolveLive(almSetting, projectAlmSetting, bitbucketCloudTokenCache);\n    if (isNotBlank(resolved.url()) && isNotBlank(resolved.repoId())) {\n      persist(dbSession, projectAlmSetting, resolved);\n    }\n    return resolved;\n  }\n\n  private void persist(DbSession dbSession, ProjectAlmSettingDto projectAlmSetting, LiveResolution resolved) {\n    try {\n      dbClient.projectAlmSettingDao().updateUrlAndRepoId(dbSession, projectAlmSetting.getUuid(), resolved.url(), resolved.repoId());\n      dbSession.commit();\n    } catch (Exception e) {\n      // Not logging \"e\" directly: some JDBC drivers embed the failing bind value in a constraint/type-violation\n      // message, and resolved.url()/resolved.repoId() originate from ALM API responses — see the equivalent\n      // comment in resolveLive for why the raw throwable never reaches the logger in this class.\n      LOG.warn(\"Failed to persist resolved url/repoId for DevOps Platform binding '{}': {} ({})\", projectAlmSetting.getUuid(),\n        sanitizeForLog(String.valueOf(e.getMessage())), e.getClass().getSimpleName());\n      // Rolls the session back to a usable state: on databases such as PostgreSQL, an aborted statement\n      // poisons the transaction until an explicit rollback, which would otherwise break every later read\n      // on this same session (e.g. the next binding in a searchProjectBindings batch).\n      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) {","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-webserver-webapi/src/main/java/org/sonar/server/almsettings/ws/ProjectBindingsServiceServerImpl.java#L217-L253","documentation":"This warning is logged by ProjectBindingsServiceServerImpl.persist when updating the resolved url and repoId of a DevOps Platform binding in the database throws. The session is explicitly rolled back (to avoid a poisoned PostgreSQL transaction) and only a sanitized message plus the exception class name is logged, because JDBC drivers may embed ALM-originated bind values in violation messages.","triggerScenarios":"resolveUrlAndRepoId resolving a binding successfully, then projectAlmSettingDao().updateUrlAndRepoId failing: DB connection issues, constraint/type violation on URL or repoId columns (e.g. value too long), dead session, or unique-constraint problems.","commonSituations":"ALM API returned a URL longer than the column size; transient DB failover during a bindings search; concurrent updates to the same binding row; schema drift after upgrade.","solutions":["Read the sanitized exception class and message from the log to distinguish connection errors from constraint violations","Check the column lengths of PROJECT_ALM_SETTINGS.URL/REPO_ID against the values your ALM returns; shorten or alter the column if too small","Verify database connectivity and re-run the binding search — persist is a cache refresh and will be retried on next resolution","If on PostgreSQL, note the code already rolls back; ensure no manual code path keeps using the aborted session"],"exampleFix":"// before: value exceeds column\nALTER TABLE project_alm_settings ALTER COLUMN url TYPE text; -- or fix data upstream\n// after: truncated/corrected binding persisted successfully","handlingStrategy":"retry","validationCode":"// Before persist, sanity-check resolved values against DB limits\nassert resolved.url() != null;\nif (resolved.url().length() > MAX_URL_COLUMN) throw new IllegalStateException(\"Resolved URL exceeds column size: \" + resolved.url().length());","typeGuard":null,"tryCatchPattern":"// Pattern used by the code itself: rollback and move on\ntry { dao.updateUrlAndRepoId(session, uuid, url, repoId); session.commit(); }\ncatch (Exception e) { log.warn(\"persist failed: {} ({})\", sanitize(e.getMessage()), e.getClass().getSimpleName()); session.rollback(); }","preventionTips":["Size URL/repoId columns generously (text) for ALM payloads","Monitor DB connectivity and failover events during binding searches","Re-run binding searches after transient DB errors — persist is idempotent cache refresh","Keep SonarQube and DB schema versions aligned after upgrades"],"tags":["alm-integration","database","persistence","jdbc","logging"],"backgroundTag":"database-write-failed","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"}