GitLab CI/CD and Headless Automation
If the previous article had a ready-made claude-code-action — a wrapper you simply add to a YAML file and everything works — GitLab is a different story. There is no pre-built "action" here, and that is actually an advantage: you work directly with the CLI via headless mode, giving you full control over every invocation.
Status: The official Claude Code integration with GitLab CI/CD is in beta and is maintained by the GitLab team. The current tracker is at gitlab.com/gitlab-org/gitlab/-/issues/573776.
Headless Mode: What Happens Under the Hood
The -p flag (or --print) turns Claude from an interactive assistant into a command-line tool: run it → the agent loop executes → it exits with code 0 or non-zero. That is headless mode.
# Simplest invocation: read and analyze
claude -p "Найди потенциальные баги в src/auth/" --allowedTools "Read,Bash"
# With JSON output — easy to parse in CI
claude -p "Проверь code style" --output-format json | jq -r '.result'
# Pass data via pipe
git diff origin/main | claude -p "Сделай ревью этого диффа" --output-format textThree flags matter most in a pipeline:
--max-turns N— limit the number of steps in the agent loop; otherwise the agent may run indefinitely--max-budget-usd N— a hard cost ceiling per invocation (available in print mode only)--permission-mode acceptEdits— allow file writes without confirmation, required for automated edits
sequenceDiagram
participant U as User / Trigger
participant GL as GitLab CI/CD
participant R as Runner (container)
participant C as claude -p
participant AG as Agent loop
participant Repo as Repository
U->>GL: Event (MR / schedule / web / @claude)
GL->>R: Job start: node:24-alpine3.21
R->>R: apk add git curl bash
R->>R: curl install.sh | bash
R->>C: claude -p "$AI_FLOW_INPUT" --allowedTools ...
loop Agent loop
C->>AG: Step planning
AG->>Repo: Read / Edit / Bash
Repo-->>AG: Tool result
AG->>C: Observation → next step
end
C-->>R: Output (text / json)
R->>Repo: git commit + push
Repo->>GL: New MR or commentBasic Setup in .gitlab-ci.yml
GitLab CI/CD cannot "download an action" — you install Claude yourself in before_script. The recommended base template:
stages:
- ai
claude:
stage: ai
image: node:24-alpine3.21
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
- 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:
# GitLab MCP Server (if configured) — provides access to the GitLab API
- /bin/gitlab-mcp-server || true
- >
claude
-p "${AI_FLOW_INPUT:-'Проанализируй изменения и предложи правки'}"
--permission-mode acceptEdits
--allowedTools "Bash Read Edit Write mcp__gitlab"
--max-turns 10
--max-budget-usd 3.00Authentication. Go to Settings → CI/CD → Variables, add ANTHROPIC_API_KEY with the "Masked" and "Protected" flags. Claude Code picks up the variable automatically — no explicit --api-key is needed.
Image. node:24-alpine3.21 is the officially recommended image. Alpine requires explicit installation of git and bash via apk; otherwise the agent will fail when it tries to work with the repository.
AI_FLOW_* Variables: Context from Threads
When a pipeline is triggered via a webhook (for example, someone wrote @claude in an MR comment), context is passed through special variables:
AI_FLOW_INPUT— the request text (what the user wrote)AI_FLOW_CONTEXT— the MR, issue, or thread identifierAI_FLOW_EVENT— the event type (note,merge_request, etc.)
In the script block this looks like:
# Log context for debugging
echo "Запрос: $AI_FLOW_INPUT"
echo "Контекст: $AI_FLOW_CONTEXT, событие: $AI_FLOW_EVENT"
claude -p "$AI_FLOW_INPUT" \
--allowedTools "Bash Read Edit Write mcp__gitlab" \
--permission-mode acceptEditsIf AI_FLOW_INPUT is not set (manual run via the UI), use a bash default substitution: ${AI_FLOW_INPUT:-'Default task'}.
Three Automation Patterns
Pattern 1: Automatic review when an MR is opened.
Triggered on the merge_request_event event, passes diffs via stdin:
mr-review:
stage: ai
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always
image: node:24-alpine3.21
before_script:
- apk add --no-cache git curl bash
- curl -fsSL https://claude.ai/install.sh | bash
script:
- >
git diff origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}...HEAD
| claude -p
"Это diff Merge Request. Найди баги, проблемы безопасности и
нарушения code style. Прокомментируй конкретные строки."
--output-format json
--max-turns 5
--max-budget-usd 2.00
| tee review.json
- jq -r '.result' review.json
artifacts:
paths: [review.json]
expire_in: 7 daysPattern 2: Scheduled codebase maintenance.
Once a week — scan for outdated TODOs and potential improvements:
weekly-audit:
stage: ai
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
image: node:24-alpine3.21
before_script:
- apk add --no-cache git curl bash
- curl -fsSL https://claude.ai/install.sh | bash
script:
- >
claude --bare
-p "Найди все TODO-комментарии старше месяца, проанализируй
их актуальность и создай список рекомендаций."
--allowedTools "Read,Bash"
--output-format json
--max-turns 8
--no-session-persistence
| jq -r '.result' > weekly-report.md
artifacts:
paths: [weekly-report.md]Pattern 3: Automated fixes on demand.
Via a web trigger with AI_FLOW_INPUT passed in:
auto-fix:
stage: ai
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
image: node:24-alpine3.21
before_script:
- apk add --no-cache git curl bash jq
- curl -fsSL https://claude.ai/install.sh | bash
# Configure git for agent commits
- git config user.email "claude-bot@example.com"
- git config user.name "Claude Bot"
script:
- >
claude
-p "${AI_FLOW_INPUT}"
--permission-mode acceptEdits
--allowedTools "Bash Read Edit Write"
--max-turns 15
--max-budget-usd 5.00
# Commit and push results
- git add -A && git commit -m "fix: automated changes by Claude" || echo "No changes"
- git push origin HEAD:${CI_COMMIT_REF_NAME}--bare: Faster Startup in CI
By default, claude -p reads CLAUDE.md, hooks, plugins, and MCP servers from the working directory. In CI this is often unnecessary and slows down startup.
The --bare flag disables all auto-discovery: CLAUDE.md, hooks, plugins, MCP servers, and auto memory. Only what you explicitly passed via flags remains. The result: reproducible, fast runs — behavior does not depend on whatever happens to be in the repository.
# Without --bare: reads everything from .claude/
claude -p "задача" --allowedTools "Read"
# With --bare: only what is explicitly passed
claude --bare -p "задача" --allowedTools "Read" --append-system-prompt "Ты лаконичный ревьюер."The official documentation notes:
--barewill become the default for-pin future versions. It is recommended to use it explicitly now.
There is one caveat: --bare also disables reading CLAUDE.md. If project context is needed, pass it via --append-system-prompt-file ./CLAUDE.md.
Enterprise: Bedrock and Vertex AI
For enterprise environments with data residency requirements — Claude Code supports the same providers as GitHub Actions. Instead of ANTHROPIC_API_KEY, OIDC authentication is used:
- Amazon Bedrock: GitLab OIDC → IAM role via
aws sts assume-role-with-web-identity, temporary credentials in environment variables. Models with a regional prefix:us.anthropic.claude-sonnet-4-6. - Google Vertex AI: Workload Identity Federation, no stored keys. Variables
GCP_WORKLOAD_IDENTITY_PROVIDERandGCP_SERVICE_ACCOUNT.
Both options require manual configuration — the one-liner quickstart does not work. Detailed YAML examples are available in the official GitLab documentation.
Practical Considerations
Cost. Unlike GitHub Actions with a fixed review cost, in GitLab you pay for runner minutes and API tokens separately. --max-budget-usd is your primary safeguard. Running on every push to an MR accumulates costs quickly; limit triggers via rules.
Parallel pipelines. Set interruptible: true at the job level so that a new push cancels the previous agent run:
claude:
interruptible: true
# ...Logs. The --debug flag in CI is your best friend when troubleshooting. Also add --output-format json and save artifacts: the total_cost_usd field in the JSON response lets you track spending per run without opening the dashboard.
CLAUDE.md in the repository. Without --bare, the agent reads it and follows its rules. This is the primary way to pass coding standards, forbidden patterns, and project-specific context — just as when running locally. See CLAUDE.md and the Memory System for details.
Security. ANTHROPIC_API_KEY must always be a masked variable. Agent changes go through MRs, so the team sees every diff and branch protection rules apply. For risks around bypassPermissions in public repositories, see The Permission Model, Security, and Trust.
See also
- GitHub Actions and Automated Code Review — an alternative approach using a ready-made action for GitHub
- Headless Mode and CLI Scripting — a complete reference for the
-pflag and CLI automation - CLAUDE.md and the Memory System — how to provide project context that the agent reads in CI
- The Permission Model, Security, and Trust — permission modes and security in automated environments
- Claude Agent SDK: Building Agents Programmatically — when you need programmatic control on top of the CLI
- Cloud Agents: Web, Routines, and Background Tasks — a CI/CD alternative for tasks that are not tied to a pipeline
- Practice: GitHub, Databases, and Web APIs via MCP — using MCP tools inside pipeline invocations