GitLab CI/CD setup
ai-pr-review supports GitLab merge requests (MRs). It uses the same container image as GitHub Actions and Bitbucket Pipelines. The GitLab path posts one summary comment per MR and updates it in place on later runs. It also posts inline discussion threads on changed lines. These threads can include suggestion fences that the MR author applies with one click.
What works
- Summary comment upsert (one note per MR, updated on each run)
- Inline MR discussion threads on changed lines
- Suggestion fences in inline discussions (GitLab’s
suggestionsyntax) - Incremental-diff SHA watermark (same HTML-comment marker as GitHub)
- Stale discussion resolution: the review resolves prior bot inline threads automatically. It resolves only threads with a first note from the bot and the
<!-- ai-pr-review-inline -->marker. - Cross-run finding dedup (issue #710): the review compares an unchanged finding with this bot’s open prior discussions (same file, within 3 lines, compatible category). It does not repost the finding and it keeps the existing discussion open. For a changed finding, the review updates the matched discussion note in place. If the severity increases, the review also adds a reply that notes the change. This dedup is narrower than GitHub’s canonical-review reuse. GitLab has no dismiss, false-positive, wont-fix, or fixed verdicts, so GitLab has no suppression. Set
AI_GITLAB_CROSS_RUN_DEDUP=falseto disable the dedup. - All existing AI agents and static analyzers (same container image)
- Self-hosted GitLab instances through
GITLAB_API_URL - Automatic retry on transient GitLab API errors (429, 502, 503, 504, and transient network errors)
What does not work on GitLab
- Slash-command triggers (see alternatives below)
- MR approval and unapproval: GitLab never posts a real approval state
Slash command alternatives
GitLab CI has no issue_comment event trigger. The /ai-pr-review slash commands (rescan, review-full, skip, dismiss, false-positive, wont-fix, fixed, feedback) work on GitHub PRs. They are not available on GitLab MRs.
Workarounds for common operations:
| GitHub slash command | GitLab equivalent |
|---|---|
/ai-pr-review rescan |
Set FORCE_FULL_DIFF=true as a CI/CD variable, then re-run the pipeline. Or delete the summary comment on the MR to reset the SHA watermark. |
/ai-pr-review review-full |
Set AI_REVIEW_MODE=full as a CI/CD variable (persists for all future runs until changed back). |
/ai-pr-review skip |
Add [skip ci] to a commit message, or set a CI/CD variable SKIP_AI_REVIEW=true and add a rules: condition to the pipeline job. |
/ai-pr-review dismiss |
Resolve individual discussion threads manually in the GitLab UI. |
/ai-pr-review fixed |
Resolve the discussion thread manually in the GitLab UI after you commit the fix. GitLab has no equivalent for a citation of the fixing commit. |
For more advanced automation, GitLab supports pipeline triggers via API and webhook events on MR notes. You can connect these to an external service that triggers review pipelines with custom variables. This needs additional infrastructure beyond the CI config.
One-time setup
1. Create a project access token
In your GitLab project, go to Settings → Access tokens and create a new project access token:
- Role: Developer (minimum role to post notes and discussions)
- Scopes:
api(required to create notes and MR discussions)
Store the token in a safe place. GitLab shows it only one time.
You can also use a personal access token with the api scope, limited to the project. The Developer role can post notes and discussions.
Token type detection: The script detects the token format automatically.
glpat-*tokens use thePRIVATE-TOKENheader.glcbt-*tokens use theJOB-TOKENheader. All other tokens (for example, OAuth2 tokens fromglab auth login) use theAuthorization: Bearerheader.
Note on
CI_JOB_TOKEN: The built-in CI job token has limited scopes. It usually cannot create MR notes or discussions. The script usesCI_JOB_TOKENifGITLAB_TOKENis not set, but this will likely fail with 403. Use a project access token for full functionality.
2. Set CI/CD variables
In your GitLab project, go to Settings → CI/CD → Variables and add:
| Name | Masked? | Protected? | Value |
|---|---|---|---|
GITLAB_TOKEN |
Yes | Optional | The project access token from step 1 |
ANTHROPIC_API_KEY |
Yes | Optional | Your Anthropic API key (or swap for your provider) |
AI_PROVIDER |
No | No | anthropic (default). Alternatives: openai, google, bedrock-proxy |
AI_REVIEW_MODE |
No | No | Empty (default): defer to .ai-pr-review/policy.yml route matching if present, else quick. Set to full to override. |
3. Copy the starter pipeline
Copy examples/pipelines/.gitlab-ci.yml to the root of your repo as .gitlab-ci.yml. Commit and push the file. The review runs on every MR open and update through merge request pipelines.
Environment variables the review reads
The starter pipeline translates the native GitLab CI variables to the canonical variables of the review. If you write your own pipeline, make sure these variables are set:
| Review var | Source (GitLab CI) |
|---|---|
VCS_PROVIDER |
Must be set to gitlab |
PR_NUMBER |
$CI_MERGE_REQUEST_IID |
MR_IID |
$CI_MERGE_REQUEST_IID |
BASE_REF |
$CI_MERGE_REQUEST_TARGET_BRANCH_NAME |
HEAD_SHA |
$CI_COMMIT_SHA |
GITLAB_PROJECT_ID |
$CI_PROJECT_ID |
GITLAB_DIFF_BASE_SHA |
$CI_MERGE_REQUEST_DIFF_BASE_SHA |
GITLAB_TOKEN |
CI/CD variable (masked) |
GITHUB_REPOSITORY |
$CI_PROJECT_PATH (used as fallback project identifier) |
AI_PROVIDER |
CI/CD variable (default anthropic) |
AI_REVIEW_MODE |
CI/CD variable (default empty: defer to policy.yml route matching, else quick. Set to full for deep agents) |
ANTHROPIC_API_KEY (or equivalent) |
CI/CD variable (masked) |
GITLAB_BOT_USERNAME |
(optional) CI/CD variable. If unset, the review detects it with GET /user. If the lookup fails, the review trusts no summary or skip marker, so it runs in full on every push. Set it when the review posts as a custom bot account. |
Note: The review needs
GITLAB_DIFF_BASE_SHAorCI_MERGE_REQUEST_DIFF_BASE_SHA. If neither is set, the review stops with a configuration error.
Forcing a full re-review: The
ai-review-rescanlabel mechanism works only in GitHub Actions. For GitLab, setFORCE_FULL_DIFF=trueas a CI/CD variable for one run. Or delete the summary comment on the MR to reset the SHA watermark.
GIT_STRATEGY: clone and GIT_DEPTH: "0" are required
The default GitLab CI clone is shallow. It can miss the base branch. The review does not run git fetch itself. It expects origin/<BASE_REF> to exist as a local ref. If the ref is missing, the git diff command fails and the review stops with a GitDiffError. Set GIT_STRATEGY: clone and GIT_DEPTH: "0" in your pipeline YAML to prevent this. The starter pipeline sets both values.
Self-hosted GitLab
For a self-hosted GitLab instance, set the GITLAB_API_URL CI/CD variable to the API base URL of your instance:
GITLAB_API_URL=https://gitlab.example.com/api/v4
The default is https://gitlab.com/api/v4.
Security considerations
Secret exposure to pipeline contributors
GITLAB_TOKEN and ANTHROPIC_API_KEY (or your provider key) are exposed as CI/CD variables to any pipeline run triggered from a branch in this project. This includes MRs opened by any user with branch-push access.
A contributor with push access to any branch can change .gitlab-ci.yml in their MR and steal these secrets. This is the classic “pwn-request” pattern.
Mitigations:
- Use a dedicated project access token with minimum scope:
apion the reviewed project only. Do not use a personal token with broader access. - Mark variables as “Protected” if your workflow needs reviews only on protected branches.
- Restrict who can push branches in Settings → Repository → Protected branches.
- Do not use this setup on a public open-source project without additional safeguards.
GITLAB_TOKEN scope
Use the minimum scope required (api). If the token has broader access (for example, admin rights), a token compromise has a much larger blast radius.
Troubleshooting
ERROR: gl_api POST /projects/.../notes -> 401
The project access token is missing, wrong, or expired. Check Settings → Access tokens for the status and expiry of the token.
ERROR: gl_api POST /projects/.../notes -> 403
The token exists but does not have the api scope, or the role of the token is too low (it needs at least Developer). If you use CI_JOB_TOKEN, it probably does not have the required scopes. Use a project access token instead.
ERROR: gl_api POST /projects/.../discussions -> 400
The inline discussion position is not valid. The line can be missing from the MR diff (for example, after a rebase). The review puts the finding in the summary comment body instead. This is normal and not fatal.
WARNING: No diff base SHA available
GITLAB_DIFF_BASE_SHA / CI_MERGE_REQUEST_DIFF_BASE_SHA is not set. This happens when the pipeline is not a merge request pipeline. Make sure your rules: block includes $CI_PIPELINE_SOURCE == "merge_request_event". Without the base SHA, the review stops with a configuration error.
ERROR: git diff against 'origin/<ref>...<sha>' failed
The review does not run git fetch. The origin/<BASE_REF> ref is not in the clone, so the git diff command fails and the review stops. Set GIT_STRATEGY: clone and GIT_DEPTH: "0" in your pipeline YAML. If you use a custom pipeline, fetch the base branch before you start the review.
Nothing posts, review exits 0
The diff is probably larger than MAX_DIFF_LINES (default 5000). The pipeline log shows Review skipped: diff too large (N lines > M). The review also posts a skip note on the MR to explain the skip.