SonarSource/sonarqube · warning

GitLab returned an access token for project

Error message

GitLab returned an access token for project '{}' expiring on '{}', within the {}-day rotation margin: it will not be reused

What it means

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.

Solutions

  1. Increase the expiry configured for minted tokens on the GitLab side (instance access settings) so new tokens exceed the rotation margin.
  2. Check the GitLab instance's maximum allowed token lifetime and align SonarQube's requested expiry.
  3. Treat this as expected if short-lived tokens are intentional; no action needed, only throughput cost of re-minting.
  4. Upgrade GitLab/policy configuration to allow longer-lived tokens if performance of repeated mints matters.

Example fix

// before (GitLab token expiry set to 3 days, margin e.g. 30)
expires_at: 2026-09-12
// after
expires_at: 2027-09-09 // beyond rotation margin, token gets cached and reused
Defensive patterns

Strategy: fallback

Validate before calling

// Java
LocalDate expiresAt = LocalDate.parse(token.expiresAt());
boolean expiringSoon = !expiresAt.isAfter(LocalDate.now().plusDays(TOKEN_ROTATION_MARGIN_DAYS));
if (expiringSoon) { /* expect re-mint per call; plan GitLab token policy accordingly */ }

Type guard

boolean exceedsRotationMargin(ScmAccessToken token) {
  return LocalDate.parse(token.expiresAt()).isAfter(LocalDate.now().plusDays(marginDays));
}

Prevention

When it happens

Trigger: 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.

Common situations: GitLab instances configured with tight token lifetime limits; tokens created near a mandated expiry date; misconfigured instance-level token expiration policy.

Related errors


AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09). Data as JSON: /api/errors/e8b01c9178835991. Report an issue: GitHub.

Appendix: source

Thrown at server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabScmAccessTokenProvider.java:135

    }

    // GitLab API calls below are network I/O, deliberately made outside the DbSession above, so a
    // pooled DB connection is not held for their duration.
    return Optional.of(getOrCreateToken(request));
  }

  private ScmAccessToken getOrCreateToken(TokenMintRequest request) {
    Optional<ScmAccessToken> cachedToken = getCachedToken(request.cacheKey);
    if (cachedToken.isPresent()) {
      return cachedToken.get();
    }
    Lock refreshLock = tokenRefreshLocks.get(request.cacheKey);
    refreshLock.lock();
    try {
      return getCachedToken(request.cacheKey).orElseGet(() -> {
        ScmAccessToken token = createToken(request);
        if (isExpiring(token)) {
          LOG.warn("GitLab returned an access token for project '{}' expiring on '{}', within the {}-day rotation margin: it will not be reused",
            request.safeProjectKey, token.expiresAt(), TOKEN_ROTATION_MARGIN_DAYS);
          return token;
        }
        tokenCache.put(request.cacheKey, token);
        return token;
      });
    } finally {
      refreshLock.unlock();
    }
  }

  private Optional<ScmAccessToken> getCachedToken(TokenCacheKey cacheKey) {
    ScmAccessToken token = tokenCache.getIfPresent(cacheKey);
    if (token == null) {
      return Optional.empty();
    }
    if (isExpiring(token)) {
      tokenCache.invalidate(cacheKey);

View on GitHub (pinned to 184c821202)