SonarSource/sonarqube · warning
GitLab returned an unparseable token expiry
Error message
GitLab returned an unparseable token expiry '{}', falling back to the requested date What it means
formatExpiresAt() parses the expiry date returned by GitLab's token-creation API response. If the date string is present but not parseable as an ISO local date (DateTimeParseException), it logs this warning and falls back to the locally requested expiry date. Token creation continues; only the recorded expiry is approximated.
Solutions
- No immediate action required — the provider falls back to the requested date and minting succeeds.
- Check the GitLab instance version and response format of POST /api/v4/personal_access_tokens; upgrade GitLab if it emits non-ISO dates.
- Inspect any middleware/proxy that might rewrite JSON fields.
- Verify the fallback date is acceptable; if not, correct the requested expiry passed to the API.
Example fix
// before (GitLab returns) expires_at: "09/09/2026" // unparseable // after (fix GitLab output or accept fallback) expires_at: "2026-09-09" // ISO_LOCAL_DATE, parsed normally
Defensive patterns
Strategy: fallback
Validate before calling
// Java
try { LocalDate.parse(gitlabExpiresAt.trim()); }
catch (DateTimeParseException e) { /* use locally requested expiry as fallback */ } Type guard
Optional<LocalDate> tryParseIsoDate(String value) {
if (value == null || value.isBlank()) return Optional.empty();
try { return Optional.of(LocalDate.parse(value.trim())); }
catch (DateTimeParseException e) { return Optional.empty(); }
} Try / catch
try {
LocalDate d = LocalDate.parse(responseExpiresAt.trim());
} catch (DateTimeParseException e) {
LOG.warn("Unparseable expiry '{}', using requested date", responseExpiresAt);
LocalDate d = requestedExpiresAt;
} Prevention
- Keep GitLab on a version that returns ISO-8601 expires_at.
- Check proxies/middleware for JSON response rewriting.
- Treat this warning as informational; verify fallback dates are sane.
- Add a test asserting the date-format contract in integration tests.
When it happens
Trigger: GitLab API returns an expires_at value in an unexpected format (null-safe but non-ISO, e.g. with timezone suffix or locale formatting), typically due to GitLab version differences or a proxy altering the response.
Common situations: Mixed GitLab versions behind API gateways; custom GitLab plugins modifying responses; clock/date format changes between GitLab releases.
Related errors
- allowAllGroups can only be enabled when Auto-provisioning…
- allowAllGroups cannot be enabled when the GitLab URL is…
- allowedGroups cannot be empty when Auto-provisioning is…
- Cannot mint a GitLab access token for project
- Cannot mint a GitLab access token: project
AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09).
Data as JSON: /api/errors/19cbff0db401bf63.
Report an issue: GitHub.
Appendix: source
Thrown at server/sonar-webserver-common/src/main/java/org/sonar/server/common/almsettings/gitlab/GitlabScmAccessTokenProvider.java:202
private static Long parseGitlabProjectId(@Nullable String almRepo, String safeProjectKey) {
if (almRepo == null || almRepo.isBlank()) {
LOG.warn("Cannot mint a GitLab access token: project '{}' has no repository configured on its DevOps Platform binding", safeProjectKey);
return null;
}
try {
return Long.parseLong(almRepo);
} catch (NumberFormatException e) {
LOG.warn("Cannot mint a GitLab access token: project '{}' has a non-numeric GitLab repository identifier '{}'", safeProjectKey, sanitizeForLog(almRepo));
return null;
}
}
private static String formatExpiresAt(@Nullable String responseExpiresAt, LocalDate requestedExpiresAt) {
if (responseExpiresAt != null && !responseExpiresAt.isBlank()) {
try {
return LocalDate.parse(responseExpiresAt.trim()).format(DateTimeFormatter.ISO_LOCAL_DATE);
} catch (DateTimeParseException e) {
LOG.warn("GitLab returned an unparseable token expiry '{}', falling back to the requested date", sanitizeForLog(responseExpiresAt));
}
}
return requestedExpiresAt.format(DateTimeFormatter.ISO_LOCAL_DATE);
}
private static String sanitizeForLog(String value) {
return CRLF_PATTERN.matcher(value).replaceAll("_");
}
private record TokenCacheKey(String projectUuid, String almSettingUuid, long gitlabProjectId, long almSettingUpdatedAt) {
}
private record TokenMintRequest(TokenCacheKey cacheKey, String safeProjectKey, AlmSettingDto almSetting) {
}
}
View on GitHub (pinned to 184c821202)