{"record":{"id":"e8b01c9178835991","repo":"SonarSource/sonarqube","slug":"gitlab-returned-an-access-token-for-project-e","errorCode":null,"errorMessage":"GitLab returned an access token for project '{}' expiring on '{}', within the {}-day rotation margin: it will not be reused","messagePattern":"GitLab returned an access token for project '(.+?)' expiring on '(.+?)', within the (.+?)-day rotation margin: it will not be reused","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabScmAccessTokenProvider.java","lineNumber":135,"sourceCode":"    }\n\n    // GitLab API calls below are network I/O, deliberately made outside the DbSession above, so a\n    // pooled DB connection is not held for their duration.\n    return Optional.of(getOrCreateToken(request));\n  }\n\n  private ScmAccessToken getOrCreateToken(TokenMintRequest request) {\n    Optional<ScmAccessToken> cachedToken = getCachedToken(request.cacheKey);\n    if (cachedToken.isPresent()) {\n      return cachedToken.get();\n    }\n    Lock refreshLock = tokenRefreshLocks.get(request.cacheKey);\n    refreshLock.lock();\n    try {\n      return getCachedToken(request.cacheKey).orElseGet(() -> {\n        ScmAccessToken token = createToken(request);\n        if (isExpiring(token)) {\n          LOG.warn(\"GitLab returned an access token for project '{}' expiring on '{}', within the {}-day rotation margin: it will not be reused\",\n            request.safeProjectKey, token.expiresAt(), TOKEN_ROTATION_MARGIN_DAYS);\n          return token;\n        }\n        tokenCache.put(request.cacheKey, token);\n        return token;\n      });\n    } finally {\n      refreshLock.unlock();\n    }\n  }\n\n  private Optional<ScmAccessToken> getCachedToken(TokenCacheKey cacheKey) {\n    ScmAccessToken token = tokenCache.getIfPresent(cacheKey);\n    if (token == null) {\n      return Optional.empty();\n    }\n    if (isExpiring(token)) {\n      tokenCache.invalidate(cacheKey);","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabScmAccessTokenProvider.java#L117-L153","documentation":"In getOrCreateToken(), after creating a fresh GitLab token, the provider checks whether its expiry falls within TOKEN_ROTATION_MARGIN_DAYS of now. If so, it logs this warning and returns the token WITHOUT caching it, so every request mints a new token instead of reusing a soon-to-expire one. This is intentional protective behavior, not a failure.","triggerScenarios":"GitLab returns a personal access token whose expires_at is already close (within the rotation margin days) when createToken() runs — e.g. the GitLab instance's max token lifetime is short, or the admin set a short expiry on the PAT used for minting.","commonSituations":"GitLab instances configured with tight token lifetime limits; tokens created near a mandated expiry date; misconfigured instance-level token expiration policy.","solutions":["Increase the expiry configured for minted tokens on the GitLab side (instance access settings) so new tokens exceed the rotation margin.","Check the GitLab instance's maximum allowed token lifetime and align SonarQube's requested expiry.","Treat this as expected if short-lived tokens are intentional; no action needed, only throughput cost of re-minting.","Upgrade GitLab/policy configuration to allow longer-lived tokens if performance of repeated mints matters."],"exampleFix":"// before (GitLab token expiry set to 3 days, margin e.g. 30)\nexpires_at: 2026-09-12\n// after\nexpires_at: 2027-09-09 // beyond rotation margin, token gets cached and reused","handlingStrategy":"fallback","validationCode":"// Java\nLocalDate expiresAt = LocalDate.parse(token.expiresAt());\nboolean expiringSoon = !expiresAt.isAfter(LocalDate.now().plusDays(TOKEN_ROTATION_MARGIN_DAYS));\nif (expiringSoon) { /* expect re-mint per call; plan GitLab token policy accordingly */ }","typeGuard":"boolean exceedsRotationMargin(ScmAccessToken token) {\n  return LocalDate.parse(token.expiresAt()).isAfter(LocalDate.now().plusDays(marginDays));\n}","tryCatchPattern":null,"preventionTips":["Configure GitLab token lifetime well above the rotation margin.","Check the instance's max token expiration policy before rollout.","Monitor this warning as a signal to lengthen token lifetimes.","Budget for re-mint cost if short-lived tokens are intentional."],"tags":["gitlab","token-expiry","caching","rotation"],"backgroundTag":"jwt-token-expired","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"}