Claude Code in GitLab CI/CD: Jobs, Triggers, and OIDC
Claude Code runs in GitLab CI/CD as an ordinary pipeline job: the job installs the Claude Code CLI on your runner, calls claude -p with a prompt, and the changes come back as a merge request that goes through your normal approvals. Anthropic documents the integration, but it is in beta and maintained by GitLab, so expect it to change and look to the linked GitLab issue for support.
This guide follows that documentation: the quick-setup job, line by line; what it takes to make @claude comments start a pipeline (it is not automatic); Bedrock and Google Cloud through OIDC with no stored keys; security and cost controls; and troubleshooting. If you use GitHub instead, the native action is covered in Claude Code GitHub Actions; for the -p flag and its options, see Claude Code CLI Reference.
The quick-setup job
Add ANTHROPIC_API_KEY under Settings > CI/CD > Variables as a masked variable (protected if only protected branches should use it), then add this job from the docs to .gitlab-ci.yml:
1stages:2 - ai3 4claude:5 stage: ai6 image: node:24-alpine3.217 rules:8 - if: '$CI_PIPELINE_SOURCE == "web"'9 - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'10 variables:11 GIT_STRATEGY: fetch12 before_script:13 - apk update14 - apk add --no-cache git curl bash15 - curl -fsSL https://claude.ai/install.sh | bash16 - export PATH="$HOME/.local/bin:$PATH"17 script:18 - /bin/gitlab-mcp-server || true19 - >20 claude21 -p "${AI_FLOW_INPUT:-'Review this MR and implement the requested changes'}"22 --permission-mode acceptEdits23 --allowedTools "Bash Read Edit Write mcp__gitlab"24 --debugReading it: rules runs the job on manual pipelines (web) and merge request events. before_script installs the CLI; the installer puts claude in ~/.local/bin, which is not on PATH in this image, hence the export. /bin/gitlab-mcp-server || true starts a GitLab MCP server if your runner image provides one and carries on if not. Then claude -p runs non-interactively with the prompt from AI_FLOW_INPUT (or a default), auto-approving edits, and allowing Bash, file tools, and the GitLab MCP tools.
Test it by running the pipeline manually from CI/CD > Pipelines.
Quick reference
--allowedTools "Bash ..."allows every shell command. Narrow it, for exampleBash(npm test *), once you know what the job needs.--debugis useful while setting up; remove it later to keep job logs shorter.- Pin the image and consider caching the CLI install; the installer downloads Claude Code on every job otherwise.
Remember this
It is a plain job: install the CLI, run claude -p with explicit tools, and let the merge request carry the result.
How @claude mentions actually trigger a job
Unlike the GitHub action, GitLab has no built-in @claude trigger in this integration. The docs describe the mention flow as an optional step you build:
1. Add a project webhook for Comments (notes) that points at an event listener you run.
2. When a comment contains @claude, the listener calls GitLab's pipeline trigger API, passing variables such as AI_FLOW_INPUT (the request), AI_FLOW_CONTEXT, and AI_FLOW_EVENT.
3. The job reads those variables and uses AI_FLOW_INPUT as the prompt.
Without a listener, the quick-setup job still works: run it manually with AI_FLOW_INPUT set in the pipeline form, or let it run on merge request events with the default prompt. Once a listener exists, the example comments from the docs work as you would expect: @claude implement this feature based on the issue description in an issue, or @claude suggest a concrete approach to cache the results of this API call in a merge request discussion.
For GitLab API operations (commenting, opening merge requests), the job uses CI_JOB_TOKEN by default. If that lacks permission, create a Project Access Token with api scope and store it as a masked GITLAB_ACCESS_TOKEN variable.
Quick reference
- The listener is the part that decides who may trigger Claude. Check the commenter's project role before calling the trigger API, or anyone who can comment can start a job.
- Pass only the comment text and IDs in
AI_FLOW_*; let the job fetch the rest through the API, so a crafted payload cannot inject context. - A comment must contain
@claude, not/claude, for the docs' listener pattern to match.
Remember this
Mentions need a webhook listener you run and the trigger API; until then, start jobs manually or on MR events.
Bedrock and Google Cloud without stored keys
For enterprise setups the docs recommend OIDC so the runner holds no long-lived credentials. GitLab mints a job OIDC token from an id_tokens: block and exposes it as GITLAB_OIDC_TOKEN; set aud to the audience you configured with the cloud provider.
Amazon Bedrock. Configure GitLab as an OIDC identity provider in AWS IAM, create a role trusted by it and restricted to your project and protected refs, and attach least-privilege Bedrock invoke permissions. Store AWS_ROLE_TO_ASSUME and AWS_REGION as variables. The job exchanges the token for temporary credentials:
1id_tokens:2 GITLAB_OIDC_TOKEN:3 aud: https://gitlab.example.com4 before_script:5 # ... install the CLI as in the quick setup, plus aws-cli and jq6 - printf "%s" "$GITLAB_OIDC_TOKEN" > /tmp/oidc_token7 - >8 aws sts assume-role-with-web-identity9 --role-arn "$AWS_ROLE_TO_ASSUME"10 --role-session-name "gitlab-claude-$(date +%s)"11 --web-identity-token "file:///tmp/oidc_token"12 --duration-seconds 3600 > /tmp/aws_creds.json13 - export AWS_ACCESS_KEY_ID="$(jq -r .Credentials.AccessKeyId /tmp/aws_creds.json)"14 - export AWS_SECRET_ACCESS_KEY="$(jq -r .Credentials.SecretAccessKey /tmp/aws_creds.json)"15 - export AWS_SESSION_TOKEN="$(jq -r .Credentials.SessionToken /tmp/aws_creds.json)"16 variables:17 CLAUDE_CODE_USE_BEDROCK: "1"Google Cloud's Agent Platform. Set up Workload Identity Federation trusting GitLab OIDC and a dedicated service account with only the needed roles. The job writes the token to a file, writes an external_account credential configuration that points at it and at the service account to impersonate, sets GOOGLE_APPLICATION_CREDENTIALS to that file, and sets CLAUDE_CODE_USE_VERTEX=1, ANTHROPIC_VERTEX_PROJECT_ID, and CLOUD_ML_REGION. No service-account key is ever downloaded.
Quick reference
- Bedrock model IDs carry region prefixes, for example
us.anthropic.claude-sonnet-4-6. - Restrict the AWS role's trust policy to specific projects and protected refs, so a job on any branch cannot assume it.
- The full Google Cloud job, including the credential JSON, is in the docs' configuration examples; copy it rather than retyping the URLs.
Remember this
Use the job's OIDC token to get short-lived cloud credentials; scope the trust to your project and protected refs.
Security and cost controls
The job runs on your runners, with the permissions of its tokens and whatever network the runner has. The docs' recommendations:
- Never commit API keys or cloud credentials; use masked (and, where appropriate, protected) CI/CD variables.
- Prefer provider OIDC over long-lived keys.
- Limit job permissions and network egress on the runner.
- Review Claude's merge requests like any other contributor's; branch protection and approvals still apply because every change goes through a merge request.
Each run costs runner time (compute minutes on your GitLab plan or your own runners) and tokens (by prompt and response size, task complexity, and codebase size). To keep both in check, write specific requests, keep CLAUDE.md focused, set --max-turns, set GitLab's job-level timeout (for example timeout: 30m), limit concurrency, and cache package installs on runners.
--permission-mode acceptEdits plus Bash in --allowedTools means the job will edit files and run commands without asking. That is the point in CI, but it makes the allowed-tools list and the runner's own isolation your real boundary.
Quick reference
- A protected variable is only exposed to pipelines on protected branches and tags; use that for the API key if merge request pipelines from forks are possible.
- Put coding standards and review criteria in
CLAUDE.mdat the repository root; Claude reads it on every run. - Run
claude --helpin the job to confirm which flags the installed version supports; the docs note flags can change between versions.
Remember this
Masked variables, OIDC, narrow allowed tools, runner isolation, and MR review are the controls; turn caps and job timeouts bound the cost.
Troubleshooting
Most failures fall into three groups: nothing triggers the pipeline (the mention flow needs your own listener), the job lacks GitLab API permissions (a token or tool problem), or authentication to the model provider fails (a variable or OIDC trust problem). Run the job manually first with a simple AI_FLOW_INPUT, so you can tell trigger problems from job problems.
For provider authentication, test outside the pipeline where you can: confirm the API key works with claude -p "hello" on your own machine, and for OIDC check the role's trust policy or the Workload Identity provider's attribute conditions against the project path and ref the job actually runs on. A mismatch there fails the token exchange before Claude Code is involved.
| Symptom | Check | Fix |
|---|---|---|
| @claude comments do nothing | Is anything triggering the pipeline? | Add the notes webhook and listener, or run the job manually |
| Job cannot comment or open an MR | CI_JOB_TOKEN permissions; mcp__gitlab allowed | Use a Project Access Token with api scope; add mcp__gitlab to --allowedTools |
| Claude lacks context about the MR | Does the job run in MR context? | Run on merge_request_event or pass AI_FLOW_* variables |
| Authentication errors (Claude API) | ANTHROPIC_API_KEY present, valid, unexpired | Re-create the masked variable |
| Authentication errors (cloud) | OIDC/WIF trust, role or impersonation, region, model access | Fix the trust conditions; confirm the model is enabled in that region |
| claude: not found | Installer path | Export $HOME/.local/bin onto PATH after installing |
Quick reference
- Keep
--debugon until the job works end to end, then remove it. - If the protected
ANTHROPIC_API_KEYis missing in a merge request pipeline, the branch is not protected; that is the variable doing its job. - Because the integration is in beta, check the GitLab issue linked from the docs when behaviour differs from what is written here.
Remember this
Run manually first to separate trigger problems from job problems; then check tokens, tools, and provider auth.
Hands-on: a manual run that opens a merge request
Use a test GitLab project with a test command.
1. Add ANTHROPIC_API_KEY as a masked variable and the quick-setup job to .gitlab-ci.yml.
2. Replace --allowedTools "Bash Read Edit Write mcp__gitlab" with --allowedTools "Bash(npm test *)" "Read" "Edit" "Write" "mcp__gitlab" (one quoted rule per argument, because the narrowed rule contains spaces) and add timeout: 20m to the job.
3. Run the pipeline manually with AI_FLOW_INPUT set to "add a unit test for the parse function and open an MR".
4. Read the job log, then review the merge request it created.
5. If the job could not open the merge request, create a Project Access Token with api scope as GITLAB_ACCESS_TOKEN and run it again.
Pass criterion: the job ran with narrowed tools and a timeout, and produced a merge request you reviewed and either merged or closed.
Quick reference
- If Claude needed a command you did not allow, the log shows the denial; add that command specifically rather than all of Bash.
- Delete the test token and variable afterwards.
- Only build the webhook listener once manual runs work reliably.
Remember this
Get one manual run with narrowed tools to produce a reviewed MR before adding mention triggers.
Key takeaway
Claude Code in GitLab CI/CD is a pipeline job that installs the CLI, runs claude -p with a prompt and an explicit tool list, and returns its work as a merge request. The integration is in beta and maintained by GitLab. @claude comments need a notes webhook and a listener you run that calls the pipeline trigger API with AI_FLOW_* variables; until you build that, run the job manually or on merge request events. For Bedrock and Google Cloud, use the job's OIDC token for short-lived credentials instead of stored keys.
The boundaries are the allowed tools, the runner's isolation and egress, the tokens' permissions, and merge request review. Cap turns and job time from the start. For the permission rule syntax used in --allowedTools, see Claude Code Permissions.
Practice (40 min): complete the five steps above, then sketch the checks your webhook listener would apply before triggering a pipeline.
References
- Claude Code docs: Claude Code GitLab CI/CD — checked Oct 17, 2026
- GitLab issue for the Claude Code integration — checked Oct 17, 2026
- GitLab Docs: CI/CD variables — checked Oct 17, 2026
Shoaib Hossain
Lead Author & Systems ArchitectSoftware engineer and distributed systems architect specializing in backend scalability, cloud-native infrastructure, databases, and AI engineering workflows. Author and maintainer of Core Concept Learning.
Related Articles
Explore this topic