Senior-level code review for GitLab merge requests. Use when: reviewing MRs, providing feedback on code quality, security, performance, maintainability, or p...
---
name: gitlab-code-review
description:
Performs structured code reviews on GitLab Merge Requests. Use when the user provides
a GitLab MR URL and wants to analyze the diff, generate engineering feedback, and/or
post inline review comments back to the MR via GitLab API. Triggers on requests like:
"review this MR", "check this merge request", "post review comments to GitLab",
"analyze the diff at a gitlab URL", "do a code review for a merge request URL".
Covers Java/Spring Boot, MongoDB, PostgreSQL, React, and TypeScript codebases.
---
# GitLab MR Code Review
## Workflow
### 1. Read credentials and check token scope
Credentials: `~/.openclaw/credentials/gitlab.json`
```json
{
"token": "glpat-xxx",
"host": "https://gitlab.com",
"ignore_patterns": ["*.min.js", "*.lock", "forms/*.json"]
}
```
Required API scopes:
- `api` — required for posting inline comments
- `read_api` — sufficient for analysis only (no comment posting)
**Always run token check first** to know upfront whether comments can be posted:
```bash
python scripts/gitlab_client.py check-token <mr_url>
```
Output includes `"can_write": true/false`. If `false`, skip step 6 and inform the user that the token needs the `api` scope to post comments. Do NOT proceed to analysis and then fail at step 6.
### 2. Fetch MR metadata and diff
```bash
python scripts/gitlab_client.py fetch-mr <mr_url>
python scripts/gitlab_client.py fetch-diff <mr_url>
```
`fetch-diff` returns a JSON array. Each entry contains `new_path`, `old_path`, `diff` (unified diff text), and boolean flags `new_file`, `deleted_file`, `renamed_file`.
> **Fallback**: if the `/diffs` endpoint returns HTTP 500 (some self-hosted GitLab instances), the script automatically retries via `/changes`. No manual intervention needed.
### 3. Filter files
Use `ignore_matcher.py` to exclude files before analysis:
```python
from ignore_matcher import filter_diffs
reviewable = filter_diffs(all_diffs) # merges defaults + credentials ignore_patterns
```
**Default ignore patterns** (always applied, even without credentials file):
`*.min.js`, `*.min.css`, `*.lock`, `package-lock.json`, `pnpm-lock.yaml`, `forms/*.json`
Binary extensions (`.png`, `.jar`, `.class`, `.map`, etc.) are always skipped.
### 4. Analyze the diff
- Analyze only modified lines (added/removed in the diff). Do not comment on unchanged context lines.
- If the total diff is large, process file-by-file and aggregate results.
- Read `references/review-guidelines.md` for all review rules, severity definitions, and comment format.
**Focus areas:**
- Java / Spring Boot — Clean Code, SOLID, transaction boundaries, lazy loading
- MongoDB — query correctness, index coverage, atomicity
- PostgreSQL — SQL correctness, isolation levels, index/schema migrations
- React / TypeScript — hooks correctness, type safety, XSS, stale closures
### 5. Structure the chat summary
Group findings by severity:
```
## Code Review — <MR title> (<source_branch> → <target_branch>)
### Critical
- `UserService.java:42` — Transaction wraps HTTP call; holds DB lock during network I/O.
### Major
- `OrderRepository.java:87` — N+1: `findRolesByUserId` called inside loop. Use batch query.
### Minor
- `PaymentDto.java:15` — Field name `val` is not descriptive.
### Decision: Needs changes
```
Decision options: **Pass** / **Needs changes** / **Reject**
- Pass: no Critical or Major findings
- Needs changes: one or more Major findings, no Critical
- Reject: one or more Critical findings
### 6. Post inline comments to GitLab
Only execute this step if `check-token` (step 1) returned `"can_write": true`.
**Write comments to a temp JSON file, then post via `post_comments.py`.**
Never use `python -c` with inline comment bodies — backticks and special characters break shell escaping.
```bash
# 1. Write all findings to a JSON file
cat > /tmp/mr_comments.json << 'EOF'
[
{
"file_path": "src/main/UserService.java",
"line": 42,
"body": "[CRITICAL] Transaction wraps HTTP call...\n\nSuggestion:\n```java\n// fix\n```"
}
]
EOF
# 2. Post via script
python scripts/post_comments.py <mr_url> /tmp/mr_comments.json
```
**How to determine the correct line number** from a diff hunk:
```
@@ -375,6 +375,8 @@ ← new file starts at line 375
unchanged line → 375
unchanged line → 376
unchanged line → 377
+ added line → 378 ← use this number
+ added line → 379
```
Count from the `+A` value in `@@ -X,Y +A,B @@` for new-file lines.
Each comment body format (from `references/review-guidelines.md` §8):
```
[SEVERITY] <one-line issue>
<2-4 sentence explanation referencing the diff.>
Suggestion:
```<language>
<corrected snippet>
```
```
**Constraints:**
- Do not auto-approve the MR.
- Do not add labels or trigger pipelines.
- Only post comment-type discussions (no approval API calls).
- If a line is not in the diff, the API returns an error — log it and continue with the next comment.
- On HTTP 403 `insufficient_scope`, the script stops immediately and prints a fix instruction. Do not retry.
## Behavior Rules
- Strict engineering tone. No emotional language. No generic praise.
- Analyze only the modified code in the diff. Do not speculate about code outside the diff.
- Do not log or persist source code content.
- Respect ignore patterns strictly.
- For large diffs: process per file, deduplicate similar findings across files before final output.
## References
- **Review rules, severity table, comment format**: `references/review-guidelines.md`
- §2 Java & Spring Boot (Clean Code, transactions, N+1, concurrency)
- §3 MongoDB (queries, indexes, atomicity)
- §4 PostgreSQL (SQL correctness, isolation, migrations)
- §5 React & TypeScript (hooks, type safety, security)
- §6 SOLID & DDD alignment
- §7 Severity classification table
- §8 Inline comment format template
don't have the plugin yet? install it then click "run inline in claude" again.
by @clawhub
added explicit decision points for token scope and decision logic, documented gitlab api rate limits and error handling (429/403/404/500), clarified line number calculation from diff hunks, separated token check as mandatory step 1 to prevent analysis-then-fail, added edge case for empty filtered diffs, and structured comment payload format with concrete examples.
performs structured code review on gitlab merge requests by analyzing diffs, identifying issues across java/spring boot, mongodb, postgresql, react, and typescript codebases, and posting inline comments back to the mr. use when reviewing mrs for code quality, security, performance, maintainability, or design patterns. triggers on requests like "review this mr", "check this merge request", "post review comments to gitlab", "analyze the diff at a gitlab url", or "do a code review for a merge request url".
gitlab credentials file (~/.openclaw/credentials/gitlab.json)
{
"token": "glpat-xxx",
"host": "https://gitlab.com",
"ignore_patterns": ["*.min.js", "*.lock", "forms/*.json"]
}
required gitlab api token scopes:
api , required for posting inline comments and fetching diffsread_api , sufficient for analysis only, no comment postingexternal connection: gitlab api
https://gitlab.com/api/v4 (or custom host from credentials)authorization: bearer glpat-xxx headerreference files:
references/review-guidelines.md , review rules, severity definitions, comment format templates for java, mongodb, postgresql, react, typescriptscripts/gitlab_client.py , cli wrapper for gitlab apiscripts/post_comments.py , comment posting scriptscripts/ignore_matcher.py , file filtering logicuser provides:
https://gitlab.com/org/repo/-/merge_requests/123)run token scope validation before any analysis to determine if inline comments can be posted.
input: gitlab credentials from ~/.openclaw/credentials/gitlab.json, mr url
command:
python scripts/gitlab_client.py check-token <mr_url>
output: json response with fields "can_write": true/false, "token_valid": true/false, "scopes": [...]
action: if can_write is false, inform user that the token lacks api scope and skip step 6 entirely. do not proceed to analysis and fail later at comment posting. continue with analysis only if token_valid is true.
input: mr url, valid gitlab token
commands:
python scripts/gitlab_client.py fetch-mr <mr_url>
python scripts/gitlab_client.py fetch-diff <mr_url>
output from fetch-mr: json object with id, iid, title, source_branch, target_branch, author, created_at
output from fetch-diff: json array of file objects. each file object contains:
new_path (string) , target file pathold_path (string) , source file path (null for new files)diff (string) , unified diff textnew_file (boolean)deleted_file (boolean)renamed_file (boolean)fallback: if /diffs endpoint returns http 500 (common on self-hosted gitlab), gitlab_client.py automatically retries via /changes endpoint. no manual intervention required.
input: all diffs from step 2, ignore patterns from credentials file (or defaults)
script:
from ignore_matcher import filter_diffs
reviewable = filter_diffs(all_diffs)
default ignore patterns (always applied):
*.min.js, *.min.css, *.lock, package-lock.json, pnpm-lock.yaml, forms/*.json.png, .jpg, .gif, .jar, .class, .o, .so, .map, etc.output: filtered list of file diffs, excluding ignored files
edge case: if filtering removes all files, skip analysis and report "no reviewable files in this mr" to user.
input: reviewable diffs from step 3, review guidelines from references/review-guidelines.md
scope: analyze only added and removed lines in the diff. do not comment on unchanged context lines.
focus areas by tech stack:
large diff handling: process file-by-file and aggregate results. deduplicate similar findings across files before final output.
output: structured findings grouped by severity (critical, major, minor) with file path, line number, issue description, and remediation suggestion.
input: all findings from step 4, mr metadata from step 2
format:
## Code Review , <MR title> (<source_branch> → <target_branch>)
### critical
- `UserService.java:42` , transaction wraps http call; holds db lock during network i/o.
### major
- `OrderRepository.java:87` , n+1 query: `findRolesByUserId` called in loop. use batch query.
### minor
- `PaymentDto.java:15` , field name `val` lacks clarity.
## decision: <needs changes | pass | reject>
decision logic:
output: formatted review summary as text or markdown
prerequisite: can_write from step 1 must be true. skip this entire step if false.
input: review findings from step 4, mr url, gitlab token
substep 6a: write comment payload to temp file
write all findings to a json file. never use shell -c with inline comment bodies due to escaping issues with backticks and special chars.
file format: /tmp/mr_comments_<timestamp>.json
[
{
"file_path": "src/main/UserService.java",
"line": 42,
"body": "[CRITICAL] transaction wraps http call; holds db lock during network i/o.\n\nthis code acquires a db transaction before making an http request. if the request times out or fails, the transaction remains open, blocking other queries.\n\nsuggestion:\n```java\nUser user = userService.fetch();\ntry (Transaction tx = db.begin()) {\n tx.update(user);\n}\n```"
},
{
"file_path": "src/repository/OrderRepository.java",
"line": 87,
"body": "[MAJOR] n+1 query detected.\n\nfor each order in a loop, the code calls `findRolesByUserId`, which executes a separate query. with 1000 orders, this becomes 1001 queries.\n\nsuggestion:\n```java\nList<Order> orders = repo.findAllWithRoles();\n```"
}
]
line number calculation from diff hunk header:
given a hunk header @@ -375,6 +375,8 @@:
+ marker are new additions. count from the starting line (375 in this case).375 + 2 = 377.-X number).example:
@@ -375,6 +375,8 @@
unchanged line → line 375
unchanged line → line 376
unchanged line → line 377
+ added line → line 378 (use this)
+ added line → line 379
substep 6b: post comments via gitlab api
command:
python scripts/post_comments.py <mr_url> /tmp/mr_comments_<timestamp>.json
output: json response with status for each comment. on success, returns array of posted comment ids and discussion urls.
error handling:
insufficient_scope , script stops immediately and prints fix instruction (add api scope to token). do not retry.constraints:
decision: can token write to gitlab?
check-token returns can_write: true → proceed with steps 1-6 including comment posting.can_write: false → report to user that token lacks api scope, complete steps 1-5 (analysis and summary), skip step 6 entirely.decision: are there reviewable files?
decision: what is the review decision?
decision: should we retry failed api calls?
/diffs endpoint → automatically retry via /changes (handled by gitlab_client.py).success output structure:
token validation result (json)
token_valid (boolean), can_write (boolean), scopes (array of strings)mr metadata (json)
id, iid, title, source_branch, target_branch, author, created_atfiltered diff files (json array)
new_path, old_path, diff (string)review summary (markdown or text)
comment posting result (json array, only if step 6 executed)
file_path, line, status (success/failed), discussion_url (on success), error_message (on failure)no source code logged or persisted. all analysis remains in memory or in temp json files that are deleted after posting.
user knows the skill worked when:
token validated , terminal output shows "token_valid": true and "can_write": true (or true/false if posting is skipped).
diff fetched and files filtered , terminal or log shows count of reviewable files and total lines of code reviewed.
review summary displayed , user sees formatted markdown output with critical, major, and minor findings grouped by file and line number, plus final decision (reject/needs changes/pass).
inline comments posted to gitlab (if applicable) , user can navigate to the mr in gitlab and see new discussion threads on the affected lines. each comment includes the severity tag (critical/major/minor), explanation, and code suggestion. discussion urls are printed to terminal after posting completes.
errors reported clearly , if token lacks scope, files cannot be fetched, or api errors occur, user sees explicit error message with actionable fix (e.g., "add api scope to gitlab token" or "retry in 60 seconds due to rate limit").
temp files cleaned up , /tmp/mr_comments_*.json files are deleted after posting to prevent credential/code leaks.
credits: original skill from clawhub. enriched per implexa standards with explicit decision points, edge case handling, api rate limiting, and line number calculation guidance.