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

  1. 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.
  2. 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.
  3. Check intermediate proxies, VPN, or WAFs that could inject HTML; bypass or whitelist the SonarQube server.
  4. 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

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


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