SonarSource/sonarqube · error · IllegalArgumentException

Could not parse GitLab answer to verify write permission. Go

Error message

Could not parse GitLab answer to verify write permission. Got a non-json payload as result.

What it means

Thrown by GitlabApplicationClient.checkWritePermission when the GitLab /markdown endpoint returns 2xx but the body is not valid JSON for a GsonMarkdown (JsonSyntaxException). SonarQube uses this endpoint to confirm the token has write-level API access.

Source

Thrown at server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java:187

  public void checkWritePermission(String gitlabUrl, String personalAccessToken) {
    String url = format("%s/markdown", gitlabUrl);

    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");
  }

View on GitHub (pinned to 184c821202)

Solutions

  1. Confirm gitlabUrl is the GitLab instance root URL
  2. Test manually: curl -X POST -H 'PRIVATE-TOKEN: <token>' <gitlabUrl>/api/v4/markdown and inspect the JSON body
  3. Check proxy/gateway configuration that could replace the JSON body with HTML

Example fix

// before: gitlabUrl = "https://gitlab.example.com/api/v4" (double path -> wrong response)
// after: gitlabUrl = "https://gitlab.example.com"; // client appends /markdown itself
Defensive patterns

Strategy: try-catch

Validate before calling

curl -sS -X POST -H "PRIVATE-TOKEN: $TOKEN" -H 'Content-Type: application/json' -d '{"text":"ping","gfm":true}' "$GITLAB_URL/api/v4/markdown" | jq -e '.rendered'  # expect JSON

Try / catch

try { gitlabClient.checkWritePermission(url, token); } catch (IllegalArgumentException e) { if (e.getMessage().contains("non-json")) { inspectProxyResponseFor(url, "/markdown"); } }

Prevention

When it happens

Trigger: checkWritePermission gets an HTTP 200 whose body fails GsonMarkdown.parseOne — HTML page, empty body, or non-standard JSON from a proxy or wrong server.

Common situations: gitlabUrl points at a proxy or wrong service returning HTML; GitLab behind a gateway that alters responses; misconfigured URL path.

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/04ac5e0f8f9ac1d2. Report an issue: GitHub.