{"record":{"id":"dd895c36915eed79","repo":"SonarSource/sonarqube","slug":"gitlab-merge-request-did-not-happen-please-check","errorCode":null,"errorMessage":"GitLab Merge Request did not happen, please check your configuration","messagePattern":"GitLab Merge Request did not happen, please check your configuration","errorType":"exception","errorClass":"GitlabServerException","httpStatus":null,"severity":"error","filePath":"server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java","lineNumber":226,"sourceCode":"    if (!response.isSuccessful()) {\n      String body = response.body().string();\n      LOG.error(\"Gitlab API call to [{}] failed with {} http code. gitlab response content : [{}]\", response.request().url(), response.code(), body);\n      if (isTokenRevoked(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token was revoked\");\n      } else if (isTokenExpired(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token is expired\");\n      } else if (isInsufficientScope(response, body)) {\n        throw new GitlabServerException(response.code(), \"Your GitLab token has insufficient scope\");\n      } else if (response.code() == HTTP_FORBIDDEN) {\n        throw new GitlabServerException(response.code(), \"Forbidden access to GitLab. Verify your token's permissions and IP restrictions.\");\n      } else if (response.code() == HTTP_TOO_MANY_REQUESTS) {\n        throw new GitlabServerException(response.code(), \"GitLab API rate limit exceeded. Try again later.\");\n      } else if (response.code() == HTTP_UNAUTHORIZED) {\n        throw new GitlabServerException(response.code(), \"Invalid personal access token\");\n      } else if (response.isRedirect()) {\n        throw new GitlabServerException(response.code(), \"Request was redirected, please provide the correct URL\");\n      } else {\n        throw new GitlabServerException(response.code(), errorMessage);\n      }\n    }\n  }\n\n  private static boolean isTokenRevoked(Response response, String body) {\n    if (response.code() == HTTP_UNAUTHORIZED) {\n      try {\n        Optional<GsonError> gitlabError = GsonError.parseOne(body);\n        return gitlabError.map(GsonError::getErrorDescription).map(description -> description.contains(\"Token was revoked\")).orElse(false);\n      } catch (JsonParseException e) {\n        // nothing to do\n      }\n    }\n    return false;\n  }\n\n  private static boolean isTokenExpired(Response response, String body) {\n    if (response.code() == HTTP_UNAUTHORIZED) {","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/SonarSource/sonarqube/blob/184c821202192afc1c599fc912d0889b69fffa53/server/sonar-alm-client/src/main/java/org/sonar/alm/client/gitlab/GitlabApplicationClient.java#L208-L244","documentation":"This is the fallback errorMessage passed into checkResponseIsSuccessful by the Merge Request decoration flow: when a GitLab API call fails and none of the specific cases (revoked/expired/insufficient scope, 403, 429, 401, redirect) apply, GitlabServerException is thrown with the caller-supplied message 'GitLab Merge Request did not happen, please check your configuration'. It means MR decoration failed for a generic HTTP error; the actual status and body are logged at ERROR level just before.","triggerScenarios":"A GitLab API call in the MR decoration path (e.g. listing MRs, updating commit status) returns a non-success status not covered by the specialized branches, so the else branch at GitlabApplicationClient.java:226 throws with this caller-provided errorMessage.","commonSituations":"MR decoration enabled but project slug/namespace wrong (404); GitLab server 5xx outage; project moved or renamed; analysis reports commit status update on a MR-less commit; unhandled GitLab error body.","solutions":["Check the SonarQube server log — the full GitLab response code and body are logged at ERROR just before this exception.","Verify the GitLab project identifier in SonarQube's project binding matches the real namespace/project path.","Confirm the automatic MR decoration settings (GitLab URL, token, project id) and re-run Test configuration.","Retry after checking GitLab instance health if the log shows a 5xx status."],"exampleFix":"// before (binding)\nproject: \"group/subgroup/app-name\"\n// after: use exact GitLab namespace/path\nproject: \"group/subgroup/app-repo\"","handlingStrategy":"try-catch","validationCode":"// Pre-flight the MR decoration prerequisites\nResponse r = call(\"GET\", gitlabUrl + \"/api/v4/projects/\" + urlEncode(projectPath) + \"/merge_requests?state=opened\", token);\nif (r.code() == 404) throw new IllegalStateException(\"GitLab project '\" + projectPath + \"' not found — check the binding's project identifier\");\nif (r.code() >= 500) throw new IllegalStateException(\"GitLab server error \" + r.code() + \" — retry later\");","typeGuard":null,"tryCatchPattern":"catch (GitlabServerException e) {\n  LOG.error(\"MR decoration failed: {} (http {})\", e.getMessage(), e.getStatus());\n  // inspect server log for the GitLab response body logged at ERROR, fix config accordingly\n}","preventionTips":["Always read the ERROR-level log line above this exception; it contains the true HTTP status and body.","Validate project slug/binding with SonarQube's configuration test before enabling decoration.","Confirm GitLab instance health when 5xx statuses appear."],"tags":["gitlab","merge-request","http","configuration","integration"],"backgroundTag":"http-error-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"}