Introduction
This post explains how to set up automated code review powered by Claude Code in a self-hosted GitLab instance, without relying on GitLab’s enterprise AI features (GitLab Duo).
All you need is:
- A Claude Code Max subscription
- A GitLab Runner with proxy access configured
This gives your team AI-powered code review, generating reports in both Markdown and PDF formats.
Prerequisites
Personal Advice
Do not use a personal subscription in CI. Use Anthropic’s API service instead of a personal subscription – a personal subscription will be banned for violating the multi-user sharing rule in the terms of service. This is based on my experience of having 2 Max accounts banned.
If accessing the Anthropic API is inconvenient, consider third-party API providers such as OpenRouter. Alternatively, Alibaba Cloud’s Qwen3 is a viable option, though in practice the report quality is not as good as Opus 4.5 – the wording is less concise and the content provides less value overall.
Subscribe to Claude Code Max
To ensure high-quality code review reports, we chose the Max plan which supports the Opus model.
For users in China, the subscription can be completed via iOS in-app purchase:
- Get a UK phone number through giffgaff, then sign up at claude.ai using a Google account
- Prepare a US Apple ID, purchase gift cards from Apple’s website and add the balance
- Download the Claude App on iPhone and complete the subscription via in-app purchase
Subscription pricing:
- Via the website (requires a US credit card)
- Max plan (5x): $100/month
- Max plan (20x): $200/month
- Via iOS in-app purchase
- Max plan (5x): $125/month
- Max plan (20x): $250/month
Network Requirements
Claude Code authenticates using an OAuth Token and requires access to Anthropic’s services. If the GitLab Runner is deployed in mainland China, you need to configure a transparent proxy so that traffic is forwarded through a proxy server in a region such as the US. Alternatively, you can use a US-based server as the Runner directly.
Implementation
Build the Docker Image
Package Claude Code and its dependencies into a Docker image to avoid running the installation on every CI job:
FROM ghcr.io/puppeteer/puppeteer:24
USER root
WORKDIR /root
RUN npm install -g @anthropic-ai/claude-code md-to-pdf
The image includes the runtime environment for both Claude Code and md-to-pdf.
Configure GitLab CI
Obtain and Configure the Token
Run claude setup-token to obtain a token starting with sk-ant-oat01-.
In the GitLab project under Settings -> CI/CD -> Variables, add:
- Key:
CLAUDE_CODE_OAUTH_TOKEN - Protected: enabled (only protected branches can read it, keeping the token secure)
- Masked: enabled
Configure Markdown-to-PDF Settings
This configuration file resolves issues with code highlighting and long lines being clipped.
In the GitLab project under Settings -> CI/CD -> Variables, add:
- Key:
MD_TO_PDF_CONFIG_FILE - Protected: enabled
- Masked: disabled
- Type: File
module.exports = {
stylesheet: [
'https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.1/github-markdown.min.css'
],
highlight_style: 'atom-one-light',
body_class: 'markdown-body',
marked_options: {
headerIds: true,
smartypants: true,
gfm: true,
breaks: false,
},
pdf_options: {
format: 'A4',
margin: {
top: '20mm',
right: '20mm',
bottom: '25mm',
left: '20mm'
},
printBackground: true,
displayHeaderFooter: true,
headerTemplate: '<div></div>',
footerTemplate: `
<div style="font-size: 10px; width: 100%; text-align: center; color: #888;">
<span class="pageNumber"></span> / <span class="totalPages"></span>
</div>
`,
},
launch_options: {
args: [
"--no-sandbox",
"--disable-setuid-sandbox"
]
},
css: `
pre,
pre code,
pre code.hljs,
code,
.hljs {
white-space: pre-wrap !important;
word-wrap: break-word !important;
word-break: break-all !important;
overflow-wrap: break-word !important;
}
pre {
max-width: 100% !important;
overflow-x: visible !important;
padding: 12px;
border-radius: 8px;
font-size: 12px;
line-height: 1;
background-color: #f6f8fa !important;
page-break-inside: avoid;
}
pre > code {
display: block !important;
max-width: 100% !important;
overflow: visible !important;
}
code {
font-family: 'Cascadia Code', 'JetBrains Mono', 'Fira Code', monospace;
}
@media print {
pre, pre code, .hljs {
white-space: pre-wrap !important;
word-break: break-all !important;
overflow: visible !important;
}
}
.markdown-body {
font-size: 12px;
max-width: 100% !important;
overflow: hidden;
}
`
};
CI Configuration
stages:
- code-review
- build-assets
- build-docker-image
- deploy
claude-code-review:
image: registry.example.com/infrastructure/claude-code:v0.0.1
tags:
- us-proxy # Runner with transparent proxy configured
stage: code-review
variables:
GIT_DEPTH: 100
script:
- git fetch --depth=20 origin main:main
- |
claude \
--model opus \
--permission-mode acceptEdits \
--allowedTools "Bash(*) Read(*) Edit(*) Write(*)" \
-p "$(cat << EOF
Perform a code review on all commits from main to ${CI_COMMIT_BRANCH}.
Requirements:
1. Write the report in Chinese to ${CI_PIPELINE_ID}_review.md, do not modify other files
2. Analyze issues that still exist in the current code state
3. For each issue, include the following:
- File path and line number (based on the current code state)
- Code snippet (using markdown code blocks)
- Issue analysis: explain why this is a problem
- Fix suggestion: provide a specific fix or code example
4. Categorize by severity: Critical, High, Medium, Low
5. Do not use emoji
6. Use markdown list format (avoid markdown tables)
7. If no issues are found, simply state that the code quality is good
Focus on:
- Potential bugs and logic errors
- Security vulnerabilities
- Performance issues
- Leftover debug code
- Type errors or type-unsafe code
Analysis strategy:
1. Analyze all commits in chronological order (oldest to newest)
2. For each issue found in a commit, check whether subsequent commits have fixed it
3. If an issue has been fixed by a later commit, do not include it in the final report
4. Only report issues that still exist in the current code state
Criteria for considering an issue fixed:
- The problematic code was deleted or rewritten in a subsequent commit
- A subsequent commit explicitly addressed the issue (e.g., fixed a bug, added missing validation)
- The code logic at the same location was optimized or improved in a subsequent commit
EOF
)"
- md2pdf --config-file ${MD_TO_PDF_CONFIG_FILE} ${CI_PIPELINE_ID}_review.md
- |
echo \
"markdown: ${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/jobs/${CI_JOB_ID}/artifacts/raw/${CI_PIPELINE_ID}_review.md"
- |
echo \
"pdf: ${CI_SERVER_URL}/${CI_PROJECT_PATH}/-/jobs/${CI_JOB_ID}/artifacts/file/${CI_PIPELINE_ID}_review.pdf"
artifacts:
paths:
- "*_review.md"
- "*_review.pdf"
when: always
rules:
- if: '$CI_COMMIT_BRANCH != "main"'
# Subsequent build and deploy stages omitted
Workflow
main -> feature/* -> release -> triggers CI code review -> review report -> staging verified -> main
Developers create feature branches from main, and ops create a release branch from main. Once development is complete, the feature branch is merged into the release branch, which triggers the code review and deploys to the staging environment. After the review report is approved and staging verification passes, the release branch is merged back into main.
Security Policy
Set the release branch as a protected branch, combined with Protected Variables:
- Only CI jobs on protected branches can read
CLAUDE_CODE_OAUTH_TOKEN - Regular feature branches cannot access the token, preventing leakage
- Only ops can delete protected branches
Code review is triggered when a development branch is merged into the release branch, and runs as the first stage in the CI pipeline.
Usage
After CI completes, the report links are printed at the end of the job log:
- Use the PDF link to preview the report online
- Use the Markdown link to download and view in VSCode (convenient for copy-pasting)
Reports are stored permanently as GitLab Artifacts.
Conclusion
Advantages of this approach:
- Low barrier: Works with GitLab CE, no enterprise edition required
- Low cost: Only requires a Claude Code Max subscription
- Secure and controllable: Token is protected via Protected Variables; code and reports stay in your self-hosted GitLab
- Flexible customization: The prompt can be adjusted to focus on what matters most to your team