Skip to content

GitLab CI/CD

Beta: Claude Code for GitLab CI/CD is currently in beta. This integration is maintained by GitLab. For issues, see the GitLab tracking issue.

Claude Code integrates with GitLab CI/CD by running as a job in your .gitlab-ci.yml. The pattern is the same as GitHub Actions, but the mechanics differ: GitLab uses masked CI/CD variables instead of secrets, $VARIABLE syntax instead of ${{ secrets.VARIABLE }}, and before_script to install Claude.


Prerequisites

  1. Add ANTHROPIC_API_KEY as a masked variable — Go to your project → Settings → CI/CD → Variables → Add variable. Set the key to ANTHROPIC_API_KEY, paste your API key as the value, and check Mask variable. Optionally check Protected to restrict it to protected branches.

  2. A runner — Any GitLab runner (shared, group, or project-level) running Docker. The examples below use node:24-alpine3.21 which is small and fast.

  3. No app installation required — Unlike GitHub Actions, GitLab CI/CD does not need a separate app. The job uses CI_JOB_TOKEN for GitLab API operations by default.


How It Works

graph TD A[MR opened / pipeline triggered] --> B[GitLab Runner picks up job] B --> C[before_script: install claude CLI] C --> D[script: claude -p prompt] D --> E[Claude executes task] E --> F{Output} F -->|--allowedTools mcp__gitlab| G[Posts comment on MR/issue] F -->|Write tool| H[Commits changes to branch] F -->|stdout| I[Captured as job log / artifact] style A fill:#1e293b,color:#7dd3fc,stroke:#334155 style B fill:#1e293b,color:#7dd3fc,stroke:#334155 style C fill:#1e293b,color:#f59e0b,stroke:#334155 style D fill:#1e293b,color:#f59e0b,stroke:#334155 style E fill:#1e293b,color:#7dd3fc,stroke:#334155 style F fill:#1e293b,color:#a78bfa,stroke:#334155 style G fill:#1e293b,color:#86efac,stroke:#334155 style H fill:#1e293b,color:#86efac,stroke:#334155 style I fill:#1e293b,color:#86efac,stroke:#334155

The mcp__gitlab tool (provided by the GitLab MCP server) is what allows Claude to post MR comments, create branches, and interact with the GitLab API. Without it, Claude can still read and write files — it just cannot communicate back to GitLab’s UI.


Template 1: MR Code Review

Triggers on merge request events. Claude reviews the diff and posts findings as an MR comment.

.gitlab-ci.yml
stages:
- ai
claude-mr-review:
stage: ai
image: node:24-alpine3.21
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
variables:
GIT_STRATEGY: fetch
before_script:
- apk update
- apk add --no-cache git curl bash
- curl -fsSL https://claude.ai/install.sh | bash
script:
- /bin/gitlab-mcp-server || true
- >
claude -p
"Review this merge request for bugs, security issues, and code quality.
Check the diff with git diff origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}...HEAD.
Post your findings as an MR comment."
--permission-mode acceptEdits
--allowedTools "Bash,Read,Edit,Write,mcp__gitlab"
--max-turns 5

What each part does:

  • rules: if: '$CI_PIPELINE_SOURCE == "merge_request_event"' — only runs on MR pipelines, not every push
  • GIT_STRATEGY: fetch — fetches the repo so Claude can read files and run git diff
  • apk add --no-cache git curl bash — Alpine doesn’t include these by default; all three are needed
  • curl -fsSL https://claude.ai/install.sh | bash — installs the Claude CLI on the runner
  • /bin/gitlab-mcp-server || true — starts the GitLab MCP server if available; || true prevents failure if it’s not present
  • --allowedTools "Bash,Read,Edit,Write,mcp__gitlab" — allows Claude to run shell commands, read/write files, and post to GitLab

Template 2: Release Notes Generator

Triggers when a tag is pushed. Claude reads the commit log and writes a release notes file.

claude-release-notes:
stage: ai
image: node:24-alpine3.21
rules:
- if: '$CI_COMMIT_TAG =~ /^v/'
variables:
GIT_STRATEGY: fetch
before_script:
- apk update
- apk add --no-cache git curl bash
- curl -fsSL https://claude.ai/install.sh | bash
script:
- /bin/gitlab-mcp-server || true
- >
claude -p
"Read the git log from the previous tag to HEAD using git log and git describe.
Write release notes to RELEASE_NOTES.md.
Group by: Features, Bug Fixes, Breaking Changes, Internal.
One sentence per item."
--allowedTools "Bash,Read,Write"
--max-turns 5
artifacts:
paths:
- RELEASE_NOTES.md
expire_in: 90 days
when: always

The artifacts block makes RELEASE_NOTES.md available for download from the GitLab pipeline UI and for use in downstream jobs.


Template 3: Scheduled Weekly Audit

Runs on a schedule (configured in GitLab CI/CD → Schedules). Claude audits the codebase and writes a report.

claude-weekly-audit:
stage: ai
image: node:24-alpine3.21
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
variables:
GIT_STRATEGY: fetch
before_script:
- apk update
- apk add --no-cache git curl bash
- curl -fsSL https://claude.ai/install.sh | bash
script:
- >
claude -p
"Audit this codebase for security anti-patterns, hardcoded credentials,
missing input validation, and files with unresolved TODO/FIXME comments.
Write findings to audit-report.md. Rate each finding: Critical/High/Medium/Low."
--permission-mode plan
--allowedTools "Bash,Read,Write"
--max-turns 10
artifacts:
paths:
- audit-report.md
expire_in: 90 days
when: always

--permission-mode plan means Claude describes what it would do but does not execute shell commands or write files — suitable for read-only audits. Remove it if you want Claude to fix issues directly.

To create the schedule: go to your project → CI/CD → Schedules → New schedule. Set cron to 0 9 * * 1 (Mondays 9 AM UTC).


Key Differences from GitHub Actions

GitHub ActionsGitLab CI/CD
Secret syntax${{ secrets.ANTHROPIC_API_KEY }}$ANTHROPIC_API_KEY (set as masked variable)
Trigger on MR eventon: pull_request:if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
Trigger on tagon: push: tags: ['v*']if: '$CI_COMMIT_TAG =~ /^v/'
Trigger on scheduleon: schedule: - cron: '...'Set in CI/CD → Schedules UI, then if: '$CI_PIPELINE_SOURCE == "schedule"'
Install Claude CLIHandled by the ActionManual: curl -fsSL https://claude.ai/install.sh | bash
Post MR commentAction handles automaticallyRequires mcp__gitlab tool + GitLab MCP server
Artifactsupload-artifact actionartifacts: paths: block in job
Runner imageGitHub-hosted UbuntuYour choice; examples use node:24-alpine3.21

Using AI_FLOW_* Variables for Comment Triggers

GitLab does not have a native “respond to @claude in comments” feature yet. The pattern used by the GitLab integration is to set up a webhook that listens for note events and calls the pipeline trigger API with context variables:

claude-comment-handler:
stage: ai
image: node:24-alpine3.21
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
when: always
variables:
GIT_STRATEGY: fetch
before_script:
- apk update
- apk add --no-cache git curl bash
- curl -fsSL https://claude.ai/install.sh | bash
script:
- /bin/gitlab-mcp-server || true
- >
claude -p
"${AI_FLOW_INPUT:-Review this MR and post your findings}"
--permission-mode acceptEdits
--allowedTools "Bash,Read,Edit,Write,mcp__gitlab"
--max-turns 10

When triggered via the pipeline API with AI_FLOW_INPUT set to the comment text, Claude receives the user’s request as its prompt. The ${AI_FLOW_INPUT:-fallback} pattern uses the variable if set, or falls back to the default string.


Cost Management

Set job timeouts to prevent runaway pipeline minutes:

claude-mr-review:
timeout: 10 minutes

Limit turns to control API token usage:

--max-turns 5

Restrict to specific branches to avoid running on every commit:

rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'

Use interruptible: true so newer pipeline runs cancel stale ones:

claude-mr-review:
interruptible: true

Enterprise: AWS Bedrock and Google Vertex AI

For air-gapped or data-residency environments, swap the API key for cloud provider credentials. The Claude CLI picks up provider configuration from environment variables.

AWS Bedrock — uses OIDC to assume a role; no static keys stored:

claude-bedrock:
stage: ai
image: node:24-alpine3.21
before_script:
- apk add --no-cache bash curl jq git python3 py3-pip
- pip install --no-cache-dir awscli
- curl -fsSL https://claude.ai/install.sh | bash
- >
aws sts assume-role-with-web-identity
--role-arn "$AWS_ROLE_TO_ASSUME"
--role-session-name "gitlab-claude-$(date +%s)"
--web-identity-token "$(cat $CI_JOB_JWT_FILE)"
--duration-seconds 3600 > /tmp/aws_creds.json
- export AWS_ACCESS_KEY_ID="$(jq -r .Credentials.AccessKeyId /tmp/aws_creds.json)"
- export AWS_SECRET_ACCESS_KEY="$(jq -r .Credentials.SecretAccessKey /tmp/aws_creds.json)"
- export AWS_SESSION_TOKEN="$(jq -r .Credentials.SessionToken /tmp/aws_creds.json)"
script:
- claude -p "Your task here" --allowedTools "Bash,Read,Write"
variables:
AWS_REGION: "us-west-2"

Required CI/CD variables: AWS_ROLE_TO_ASSUME, AWS_REGION.