SonarSource/sonarqube · error · IllegalArgumentException
Could not validate GitLab write permission. Got an unexpecte
Error message
Could not validate GitLab write permission. Got an unexpected answer.
What it means
checkWritePermission calls a GitLab API to verify the token can write to a project, and parses the JSON reply with GsonMarkdown.parseOne. If the reply is valid HTTP but the body is not JSON, JsonSyntaxException is caught and this IllegalArgumentException is thrown, meaning SonarQube got an 'unexpected answer' and cannot confirm write permission. It signals a non-GitLab (or misbehaving proxy) response rather than an actual permission denial.
Source
Thrown at server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java:190
LOG.debug("verify write permission by formating some markdown : [{}]", url);
Request.Builder builder = new Request.Builder()
.url(url)
.addHeader(PRIVATE_TOKEN, personalAccessToken)
.addHeader("Content-Type", MediaTypes.JSON)
.post(RequestBody.create("{\"text\":\"validating write permission\"}".getBytes(UTF_8)));
Request request = builder.build();
String errorMessage = "Could not validate GitLab write permission. Got an unexpected answer.";
try (Response response = client.newCall(request).execute()) {
checkResponseIsSuccessful(response, errorMessage);
GsonMarkdown.parseOne(response.body().string());
} catch (JsonSyntaxException e) {
throw new IllegalArgumentException("Could not parse GitLab answer to verify write permission. Got a non-json payload as result.");
} catch (IOException e) {
logException(url, e);
throw new IllegalArgumentException(errorMessage);
}
}
private static String urlEncode(String value) {
try {
return URLEncoder.encode(value, UTF_8.toString());
} catch (UnsupportedEncodingException ex) {
throw new IllegalStateException(ex.getCause());
}
}
protected static void checkResponseIsSuccessful(Response response) throws IOException {
checkResponseIsSuccessful(response, "GitLab Merge Request did not happen, please check your configuration");
}
protected static void checkResponseIsSuccessful(Response response, String errorMessage) throws IOException {
if (!response.isSuccessful()) {View on GitHub (pinned to 184c821202)
Solutions
- Verify the GitLab API URL in ALM settings points to the GitLab instance root (e.g. https://gitlab.example.com) and that opening <url>/api/v4/user with the token returns JSON.
- Curl the failing endpoint with the same token: curl -H 'PRIVATE-TOKEN: <token>' <gitlab-url>/api/v4/projects/<id>/members/all/<userId> and inspect whether the answer is JSON.
- Check intermediate proxies, VPN, or WAFs that could inject HTML; bypass or whitelist the SonarQube server.
- Confirm the GitLab version is supported; very old versions may return unexpected payloads for the permission endpoint.
Example fix
// before (admin settings) gitlab.url = https://gitlab.example.com/users/sign_in // after gitlab.url = https://gitlab.example.com
Defensive patterns
Strategy: validation
Validate before calling
// Verify the GitLab URL serves JSON API responses before configuring/using it
try (Response r = httpClient.newCall(new Request.Builder()
.url(gitlabUrl + "/api/v4/version")
.header("PRIVATE-TOKEN", token).build()).execute()) {
String ct = r.header("Content-Type", "");
if (!r.isSuccessful() || !ct.contains("application/json")) {
throw new IllegalStateException("GitLab URL does not return JSON API responses: " + r.code() + " " + ct);
}
} Prevention
- Always configure the GitLab instance root URL, never a login/SSO page.
- Test the GitLab binding with SonarQube's 'Check configuration' button before relying on it.
- Keep proxies/WAFs from rewriting API responses; allow-list the SonarQube server.
When it happens
Trigger: An HTTP request to the GitLab members/permission endpoint returns 200 with a non-JSON body (e.g. an HTML login page, HTML error page from a reverse proxy, or an empty body), causing Gson to throw JsonSyntaxException which is converted to this error at GitlabApplicationClient.java:190.
Common situations: GitLab base URL pointing at a web UI or SSO redirect endpoint instead of the API root; corporate proxies/captive portals returning HTML; wrong port or path in GitLab URL configuration; GitLab availability page or WAF block page returned with 200.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- Could not parse GitLab answer when creating a project access
- Forbidden access to GitLab. Verify your token's permissions
- Request was redirected, please provide the correct URL
- GitLab Merge Request did not happen, please check your confi
- SonarQube was not able to retrieve resources from external s
AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09).
Data as JSON: /api/errors/fce17f2453f306f8.
Report an issue: GitHub.