---
title: "Claude Code GitLab CI — team setup and credentials · Selan"
description: "How to run Claude Code in GitLab CI for a team: which credential to store, why a protected variable can miss an MR, how to cap a run, a token per job."
url: https://selan.ai/guides/claude-code-gitlab-ci
updated: 2026-10-28T09:52:33+02:00
---

Guide · Claude Code for teams

# How do you run Claude Code in GitLab CI for a team?

Add a job to `.gitlab-ci.yml` that installs Claude Code and runs `claude -p`, with the credential in a masked CI/CD variable. Anthropic documents an API key, or OIDC into Amazon Bedrock or Google Cloud with no stored key. Cap each run with `--max-turns` and a job `timeout`. Through Selan, each job carries a token of its own.

## How do you set up Claude Code in GitLab CI?

Add `ANTHROPIC_API_KEY` under **Settings → CI/CD → Variables**, and mark it masked. Then add a job that installs Claude Code and gives it a prompt. This is Anthropic's quick setup, shortened:

```yaml
claude:
  stage: ai
  image: node:24-alpine3.21
  rules:
    - if: '$CI_PIPELINE_SOURCE == "web"'
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  before_script:
    - apk add --no-cache git curl bash
    - curl -fsSL https://claude.ai/install.sh | bash
    - export PATH="$HOME/.local/bin:$PATH"
  script:
    - claude -p "Review this MR" --permission-mode acceptEdits --allowedTools "Bash Read Edit Write mcp__gitlab"
```

The integration is in beta, and GitLab maintains it. It is the Claude Code CLI in a job, not an app you install.

`@claude` mentions need a listener of your own. A project webhook for comments calls it, and it calls the pipeline trigger API with variables such as `AI_FLOW_INPUT` and `AI_FLOW_CONTEXT`.

## Which credential should the job use?

Anthropic's GitLab and authentication documentation name four:

| Credential | Where it comes from | Who pays |
| --- | --- | --- |
| `ANTHROPIC_API_KEY` | An API key from the Claude Console, in a masked variable | The Console organisation, per token |
| `CLAUDE_CODE_OAUTH_TOKEN` | `claude setup-token`, valid for one year, on Pro, Max, Team or Enterprise | The subscription of the person who ran it |
| Amazon Bedrock | The job's GitLab OIDC token from `id_tokens:`, exchanged for temporary credentials of an IAM role | Your AWS account |
| Google Cloud's Agent Platform | The same OIDC token, through Workload Identity Federation to a service account | Your Google Cloud project |

The two OIDC routes store no long-lived key in GitLab at all. Anthropic's GitLab page advises them where possible.

A key every project's jobs share should belong to the company, not to whoever ran setup-token.

**Good to know:** a protected variable reaches only pipelines on protected branches and tags. A merge request pipeline gets it only when both its branches are protected, in the same project, and the project's settings allow it. Otherwise a review job on a feature branch runs with no credential.

## Why can't the job comment on the merge request?

Anthropic's setup posts through the job's `CI_JOB_TOKEN`, or a project access token with `api` scope. On GitLab.com Free, neither can post a comment:

-   `CI_JOB_TOKEN` cannot create a note.
-   On GitLab.com Free, neither a project nor a group access token can be issued.
-   What is left is a personal token, which posts as that person and carries everything they can do.

A job can skip the API instead. A Code Quality report, declared under `artifacts:reports:codequality`, is read by GitLab itself, with no token. It shows in the merge request's **Reports** tab. The inline markers in the diff need GitLab Ultimate.

## What does a run cost, and how do you cap it?

Each run uses two things:

-   **Runner compute minutes**, under your GitLab plan's runner billing.
-   **Model tokens**, which grow with the prompt, the task and the size of the codebase.

Anthropic's advice for keeping both down:

-   Set `--max-turns` to bound how long Claude works.
-   Set the job's `timeout`, for example `timeout: 30m`.
-   Limit how many runs go in parallel.
-   Keep `CLAUDE.md` short, because Claude reads it on every run.

None of these is a budget. They bound one run, not the month across every project. What a developer's Claude Code costs a day is in [the cost-per-developer guide](https://selan.ai/guides/claude-code-cost-per-developer).

## What should the job keep away from Claude?

GitLab hands every job variable to every line of the script. An agent that reads text a merge request wrote can be talked into printing them.

-   **Start the agent from an empty environment.** `env -i` passes only the variables it names.
-   **Remove the job token from disk.** On the docker executor, the runner writes `CI_JOB_TOKEN` into `.gitlab-runner.ext.conf` beside the checkout.
-   **Do not run as root with permissions skipped.** Claude Code refuses `--dangerously-skip-permissions` at uid 0, and container images run as root, so the job drops to a user.

Each of these steps, with the reason for it, is in [the GitLab CI review pipeline](https://selan.ai/docs/agents/code-review#gitlab).

## How does Selan run Claude Code in GitLab CI?

The job installs `selan` and Claude Code, then runs an agent from the repository's `.claude/agents/` with `selan agent-run`. It signs in with a `SELAN_TOKEN` variable instead of a provider key.

-   **No provider key in GitLab.** The company's provider accounts stay on Selan, and the job holds only its token.
-   **A token per job.** An owner mints each one under Settings → API tokens. It spends as itself, under an address like `ci-review@tokens.selan.ai`, so its runs show up as that job in Usage and Logs.
-   **A limit per job.** The company's per-person spend limit applies to a token as it does to a person, and a token can be given a limit of its own. How limits work is on [the spend page](https://selan.ai/solutions/spend).
-   **API keys only, if you want.** An owner can switch a token to API only. Selan then serves it only from the company's own API keys.
-   **One revoke.** Revoking a token ends that job's access without touching any other job.

How the token works is in [Agents in CI](https://selan.ai/docs/agents). The complete GitLab pipeline, which posts its findings as a Code Quality report, is on [the code review page](https://selan.ai/docs/agents/code-review#gitlab).

## When is Anthropic's GitLab setup the better choice?

When the job should change code. Anthropic's examples write a branch and open a merge request from an issue or a comment. Selan's documented GitLab pipeline runs an agent that writes a file, and the pipeline decides what to do with it.

When you want no stored secret at all. The Bedrock and Google Cloud routes exchange the job's OIDC token, while a Selan job still holds a `SELAN_TOKEN`.

When one project uses it, a single masked API key is simple and enough. Selan earns its place once several projects and jobs share the bill and you want each one shown and capped on its own, next to the people on the team.

[Start for free](https://selan.ai/pricing)

Free for up to three seats, and the Free plan needs no card. A CI token takes a seat of its own on a paid plan; the Free plan is people only.

### The short version

-   Claude Code in GitLab CI is a job that installs the CLI and runs `claude -p`; GitLab maintains it, in beta.
-   A masked API key is the quick route; OIDC into Bedrock or Google Cloud stores no key at all.
-   A protected variable skips merge request pipelines unless both branches are protected and access is allowed.
-   Cap each run with `--max-turns` and a job `timeout`; neither is a monthly budget.
-   Through Selan, each job carries its own token, shows up as itself in Usage and can have a limit of its own.

How the integration works is as Anthropic's GitLab CI/CD and authentication documentation (code.claude.com/docs/en/gitlab-ci-cd and /authentication) and GitLab's CI/CD variables and merge request pipeline documentation stated it on 28 October 2026. It changes: check the date before relying on it. What Selan does is as its CLI, control app and docs stated it on that date.
