Skip to content

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.md and existing patterns guide the work.
  • Small footprint. One job in .gitlab-ci.yml and 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

  1. 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.
  2. Claude runs on your chosen provider. Claude API, Bedrock (IAM, cross-region options) or Agent Platform (Workload Identity Federation).
  3. 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

  1. 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.
  2. 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 claude into ~/.local/bin, which is not on the image's PATH, hence the export.
  • AI_FLOW_INPUT, AI_FLOW_CONTEXT and AI_FLOW_EVENT are variables your trigger passes in (see mention triggers below). The fallback prompt covers manual runs.
  • --permission-mode acceptEdits lets Claude edit without prompting; --allowedTools limits what it can use. mcp__gitlab only matters if you run a GitLab MCP server.

Production setup

  1. Provider access.
    • Claude API: ANTHROPIC_API_KEY as 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.
  2. GitLab API credentials. CI_JOB_TOKEN is the default. If it lacks permissions, create a Project Access Token with api scope and store it as a masked GITLAB_ACCESS_TOKEN.
  3. The job. Use the quick start job for the Claude API, or a provider job below.
  4. 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 as AI_FLOW_INPUT (the request) and AI_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:

VariableValue
GCP_WORKLOAD_IDENTITY_PROVIDERProvider resource name without the //iam.googleapis.com/ prefix, e.g. projects/123456789/locations/global/workloadIdentityPools/gitlab/providers/gitlab-oidc
GCP_SERVICE_ACCOUNTService account email
GCP_PROJECT_IDProject ID
CLOUD_ML_REGIONRegion, 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

KnobWherePurpose
-p "..."CLI flagThe instruction
--max-turnsCLI flagCaps iterations
--permission-modeCLI flage.g. acceptEdits for unattended edits
--allowedToolsCLI flagRestricts tools
timeout: 30mGitLab job keywordCaps total job time
ANTHROPIC_API_KEYVariableClaude API only
CLAUDE_CODE_USE_BEDROCK, AWS_REGIONVariablesBedrock
CLAUDE_CODE_USE_VERTEX, ANTHROPIC_VERTEX_PROJECT_ID, CLOUD_ML_REGIONVariablesAgent 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_KEY or the provider variables present for this branch (protected variables only reach protected refs)?
  • Is it @claude rather than /claude, and is your listener matching it?

The job cannot comment or open MRs.

  • Does CI_JOB_TOKEN have enough permission? If not, use a Project Access Token with api scope.
  • Is mcp__gitlab included 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 aud value, role impersonation, variable names, region and model availability.