GitLab CI/CD
Run Claude Code as a GitLab CI/CD job that turns issue and merge request requests into branches and MRs, on the Claude API, Bedrock or Agent Platform.
Claude Code can run as a job in your GitLab pipelines. Point it at an issue or merge request, give it a prompt (often taken from a comment that mentions @claude), and it works in a branch on your own runners, then opens or updates an MR so the change goes through your normal approvals.
Note: The GitLab integration is in beta and is maintained by GitLab, not Anthropic. Support goes through GitLab's issue tracker (issue 573776 on gitlab.com). It is built on the Claude Code CLI and the Agent SDK.
Why I like it on GitLab projects
- Requests become MRs. Describe the change, get a merge request with the diff and an explanation.
- It follows your conventions.
CLAUDE.mdand existing patterns guide the work. - Small footprint. One job in
.gitlab-ci.ymland a masked variable. - Your cloud, your rules. Claude API, Amazon Bedrock or Google Cloud's Agent Platform, with regional endpoints for latency and data residency.
- Your guard rails. Runs on your runners, in containers, with branch protection and approvals intact.
How it fits together
- A trigger fires. Manual pipeline runs, MR events, or a webhook listener that calls the pipeline trigger API when a comment contains
@claude. The job gathers context from the thread and repository and builds a prompt. - Claude runs on your chosen provider. Claude API, Bedrock (IAM, cross-region options) or Agent Platform (Workload Identity Federation).
- Execution is contained. Each run is a container with network and filesystem limits, and Claude Code's workspace-scoped permissions restrict writes. All changes arrive as an MR.
Typical jobs: implementing a feature from an issue, fixing a bug reported in a comment, investigating a performance regression, or iterating on review feedback.
Quick start with the Claude API
- In the project, go to Settings > CI/CD > Variables and add
ANTHROPIC_API_KEY. Mark it masked, and protected if it should only reach protected branches. - Add a job. Here is the one I start from:
stages:
- assist
claude:
stage: assist
image: node:24-alpine3.21
timeout: 30m
rules:
- if: '$CI_PIPELINE_SOURCE == "web"' # manual "Run pipeline"
- if: '$CI_PIPELINE_SOURCE == "trigger"' # webhook listener
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
variables:
GIT_STRATEGY: fetch
before_script:
- apk add --no-cache git curl bash
- curl -fsSL https://claude.ai/install.sh | bash
- export PATH="$HOME/.local/bin:$PATH" # installer puts claude here
script:
- /bin/gitlab-mcp-server || true # only if your runner image provides one
- echo "Event=$AI_FLOW_EVENT Context=$AI_FLOW_CONTEXT"
- >
claude -p "${AI_FLOW_INPUT:-Review this merge request and apply any changes requested in its discussion}"
--permission-mode acceptEdits
--allowedTools "Bash Read Edit Write mcp__gitlab"
--max-turns 20
Run it from CI/CD > Pipelines > Run pipeline, or open an MR, and Claude will propose changes in a branch.
Notes on that job:
- The native installer drops
claudeinto~/.local/bin, which is not on the image'sPATH, hence theexport. AI_FLOW_INPUT,AI_FLOW_CONTEXTandAI_FLOW_EVENTare variables your trigger passes in (see mention triggers below). The fallback prompt covers manual runs.--permission-mode acceptEditslets Claude edit without prompting;--allowedToolslimits what it can use.mcp__gitlabonly matters if you run a GitLab MCP server.
Production setup
- Provider access.
- Claude API:
ANTHROPIC_API_KEYas a masked variable. - Bedrock: configure GitLab as an AWS OIDC provider and create an IAM role.
- Agent Platform: configure Workload Identity Federation for GitLab in Google Cloud.
- Claude API:
- GitLab API credentials.
CI_JOB_TOKENis the default. If it lacks permissions, create a Project Access Token withapiscope and store it as a maskedGITLAB_ACCESS_TOKEN. - The job. Use the quick start job for the Claude API, or a provider job below.
- Mention triggers (optional). Add a project webhook for Comments (notes) pointing at a small listener you run. When a comment contains
@claude, the listener calls the pipeline trigger API with variables such asAI_FLOW_INPUT(the request) andAI_FLOW_CONTEXT(issue or MR reference).
What a request looks like
On an issue:
@claude implement the CSV import described above. Reject rows with an invalid VAT number and report them at the end.
In an MR thread:
@claude the N+1 query in OrderSerializer is back. Prefetch line items and add a query-count test.
Claude reads the context, works on a branch and opens or updates the MR.
Amazon Bedrock
You need: Bedrock access to the Claude models you want; GitLab registered as an OIDC identity provider in IAM; an IAM role with least-privilege Bedrock invoke permissions whose trust policy is restricted to your project and protected refs; and CI/CD variables AWS_ROLE_TO_ASSUME (role ARN) and AWS_REGION.
GitLab mints an OIDC token through id_tokens:. Set aud to whatever audience you configured on the IAM identity provider (often your GitLab URL). The job swaps that token for temporary AWS credentials, so no static keys are stored:
claude-bedrock:
stage: assist
image: node:24-alpine3.21
timeout: 30m
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.northwind.example
variables:
AWS_REGION: eu-west-1
CLAUDE_CODE_USE_BEDROCK: "1"
before_script:
- apk add --no-cache bash curl jq git aws-cli
- curl -fsSL https://claude.ai/install.sh | bash
- export PATH="$HOME/.local/bin:$PATH"
- |
printf '%s' "$GITLAB_OIDC_TOKEN" > /tmp/web-identity-token
creds=$(aws sts assume-role-with-web-identity \
--role-arn "$AWS_ROLE_TO_ASSUME" \
--role-session-name "gitlab-${CI_PROJECT_ID}-${CI_JOB_ID}" \
--web-identity-token file:///tmp/web-identity-token \
--duration-seconds 3600)
export AWS_ACCESS_KEY_ID=$(jq -r .Credentials.AccessKeyId <<<"$creds")
export AWS_SECRET_ACCESS_KEY=$(jq -r .Credentials.SecretAccessKey <<<"$creds")
export AWS_SESSION_TOKEN=$(jq -r .Credentials.SessionToken <<<"$creds")
script:
- /bin/gitlab-mcp-server || true
- >
claude -p "${AI_FLOW_INPUT:-Implement the requested change and open a merge request}"
--model eu.anthropic.claude-sonnet-4-6
--permission-mode acceptEdits
--allowedTools "Bash Read Edit Write mcp__gitlab"
--max-turns 20
Bedrock model IDs carry a region-group prefix such as us. or eu.; pick the one matching where you have access. See Amazon Bedrock.
Google Cloud's Agent Platform
You need: a project with the Agent Platform API enabled; Workload Identity Federation trusting GitLab OIDC; a dedicated service account with only the Agent Platform roles it needs, impersonable by the WIF principal; and the IAM Credentials and STS APIs enabled.
CI/CD variables:
| Variable | Value |
|---|---|
GCP_WORKLOAD_IDENTITY_PROVIDER | Provider resource name without the //iam.googleapis.com/ prefix, e.g. projects/123456789/locations/global/workloadIdentityPools/gitlab/providers/gitlab-oidc |
GCP_SERVICE_ACCOUNT | Service account email |
GCP_PROJECT_ID | Project ID |
CLOUD_ML_REGION | Region, e.g. europe-west1 |
The job writes the OIDC token to a file, then an external_account credential configuration tells Google's libraries to read it and impersonate the service account. Pointing GOOGLE_APPLICATION_CREDENTIALS at that configuration hands it to Claude Code via Application Default Credentials:
claude-agent-platform:
stage: assist
image: gcr.io/google.com/cloudsdktool/google-cloud-cli:slim
timeout: 30m
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.northwind.example
variables:
CLAUDE_CODE_USE_VERTEX: "1"
ANTHROPIC_VERTEX_PROJECT_ID: "$GCP_PROJECT_ID"
CLOUD_ML_REGION: europe-west1
before_script:
- apt-get update && apt-get install -y --no-install-recommends git jq && apt-get clean
- curl -fsSL https://claude.ai/install.sh | bash
- export PATH="$HOME/.local/bin:$PATH"
- printf '%s' "$GITLAB_OIDC_TOKEN" > /tmp/oidc_token
- |
jq -n \
--arg aud "//iam.googleapis.com/${GCP_WORKLOAD_IDENTITY_PROVIDER}" \
--arg sa "$GCP_SERVICE_ACCOUNT" \
'{
type: "external_account",
audience: $aud,
subject_token_type: "urn:ietf:params:oauth:token-type:jwt",
token_url: "https://sts.googleapis.com/v1/token",
credential_source: { file: "/tmp/oidc_token" },
service_account_impersonation_url:
("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/" + $sa + ":generateAccessToken")
}' > /tmp/wif.json
- export GOOGLE_APPLICATION_CREDENTIALS=/tmp/wif.json
- gcloud auth login --cred-file=/tmp/wif.json
- gcloud config set project "$GCP_PROJECT_ID"
script:
- /bin/gitlab-mcp-server || true
- >
claude -p "${AI_FLOW_INPUT:-Review and update the code as requested}"
--permission-mode acceptEdits
--allowedTools "Bash Read Edit Write mcp__gitlab"
--max-turns 20
No service account keys are stored. Keep trust conditions specific to the project, and the service account minimal. See Google Cloud's Agent Platform.
Good practice
CLAUDE.md. Put coding standards, review criteria and project rules at the repository root; Claude reads it on every run. Keep it short, because it is loaded every time. See memory.
Security.
Warning: Never commit API keys or cloud credentials. Use masked (and where appropriate, protected) CI/CD variables, prefer OIDC over long-lived keys, restrict job permissions and network egress, and review Claude's MRs like anyone else's.
Performance. Clear issue and MR descriptions mean fewer iterations. Cache package installs on runners where you can.
Costs. You pay twice: runner minutes for the job, and tokens for each Claude interaction (scaling with prompt size, task complexity and codebase size). Keep both in check with specific requests, --max-turns, a job-level timeout:, and limits on concurrent pipelines. See costs.
Useful knobs
| Knob | Where | Purpose |
|---|---|---|
-p "..." | CLI flag | The instruction |
--max-turns | CLI flag | Caps iterations |
--permission-mode | CLI flag | e.g. acceptEdits for unattended edits |
--allowedTools | CLI flag | Restricts tools |
timeout: 30m | GitLab job keyword | Caps total job time |
ANTHROPIC_API_KEY | Variable | Claude API only |
CLAUDE_CODE_USE_BEDROCK, AWS_REGION | Variables | Bedrock |
CLAUDE_CODE_USE_VERTEX, ANTHROPIC_VERTEX_PROJECT_ID, CLOUD_ML_REGION | Variables | Agent Platform |
Flags can change between versions; run claude --help in the job to see what your version supports, and see the CLI reference and headless mode. For different jobs (review, implement, refactor), give each its own -p prompt.
For reviewing an MR from your laptop rather than CI, /code-review can post findings to a GitLab merge request with --comment using the glab CLI; see Code Review.
Troubleshooting
No reaction to @claude.
- Is a pipeline actually being triggered (manual, MR event, or your notes webhook listener)?
- Are
ANTHROPIC_API_KEYor the provider variables present for this branch (protected variables only reach protected refs)? - Is it
@clauderather than/claude, and is your listener matching it?
The job cannot comment or open MRs.
- Does
CI_JOB_TOKENhave enough permission? If not, use a Project Access Token withapiscope. - Is
mcp__gitlabincluded in--allowedTools? - Does the job have MR context, either from running in an MR pipeline or via
AI_FLOW_*variables?
Authentication errors.
- Claude API: is the key valid and unexpired?
- Bedrock or Agent Platform: check the OIDC or WIF trust, the
audvalue, role impersonation, variable names, region and model availability.