{"record":{"id":"fce17f2453f306f8","repo":"SonarSource/sonarqube","slug":"could-not-validate-gitlab-write-permission-got-an","errorCode":null,"errorMessage":"Could not validate GitLab write permission. Got an unexpected answer.","messagePattern":"Could not validate GitLab write permission\\. Got an unexpected answer\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java","lineNumber":190,"sourceCode":"\n    LOG.debug(\"verify write permission by formating some markdown : [{}]\", url);\n    Request.Builder builder = new Request.Builder()\n      .url(url)\n      .addHeader(PRIVATE_TOKEN, personalAccessToken)\n      .addHeader(\"Content-Type\", MediaTypes.JSON)\n      .post(RequestBody.create(\"{\\\"text\\\":\\\"validating write permission\\\"}\".getBytes(UTF_8)));\n\n    Request request = builder.build();\n\n    String errorMessage = \"Could not validate GitLab write permission. Got an unexpected answer.\";\n    try (Response response = client.newCall(request).execute()) {\n      checkResponseIsSuccessful(response, errorMessage);\n      GsonMarkdown.parseOne(response.body().string());\n    } catch (JsonSyntaxException e) {\n      throw new IllegalArgumentException(\"Could not parse GitLab answer to verify write permission. Got a non-json payload as result.\");\n    } catch (IOException e) {\n      logException(url, e);\n      throw new IllegalArgumentException(errorMessage);\n    }\n\n  }\n\n  private static String urlEncode(String value) {\n    try {\n      return URLEncoder.encode(value, UTF_8.toString());\n    } catch (UnsupportedEncodingException ex) {\n      throw new IllegalStateException(ex.getCause());\n    }\n  }\n\n  protected static void checkResponseIsSuccessful(Response response) throws IOException {\n    checkResponseIsSuccessful(response, \"GitLab Merge Request did not happen, please check your configuration\");\n  }\n\n  protected static void checkResponseIsSuccessful(Response response, String errorMessage) throws IOException {\n    if (!response.isSuccessful()) {","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java#L172-L208","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (admin settings)\ngitlab.url = https://gitlab.example.com/users/sign_in\n// after\ngitlab.url = https://gitlab.example.com","handlingStrategy":"validation","validationCode":"// Verify the GitLab URL serves JSON API responses before configuring/using it\ntry (Response r = httpClient.newCall(new Request.Builder()\n        .url(gitlabUrl + \"/api/v4/version\")\n        .header(\"PRIVATE-TOKEN\", token).build()).execute()) {\n  String ct = r.header(\"Content-Type\", \"\");\n  if (!r.isSuccessful() || !ct.contains(\"application/json\")) {\n    throw new IllegalStateException(\"GitLab URL does not return JSON API responses: \" + r.code() + \" \" + ct);\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["gitlab","http","json","network","configuration"],"backgroundTag":"invalid-json-response","analyzedSha":"184c821202192afc1c599fc912d0889b69fffa53","analyzedAt":"2026-09-09T12:23:51.573Z","contentChangedAt":"2026-09-09T12:23:51.573Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}