Claude Code in CI is useful exactly to the extent its rights are granted narrowly. The official GitHub Action is anthropics/claude-code-action@v1; it itself chooses the interactive-mention or immediate-automation mode by the trigger and config. The main inputs are prompt, the anthropic_api_key or cloud authentication and claude_args for flags. But the main thing in CI is not what the agent can do but what it is allowed to do: least privilege here is not a recommendation but a condition of security.
A sensible workflow starts with minimal permissions. It helps to see a whole example once: a trigger on pull_request, permissions of contents read and pull-requests write, concurrency with cancellation of previous runs, checkout with persist-credentials false and a call to the action with a prompt and narrow claude_args. The key is kept only in GitHub Secrets, and claude_args limits max-turns and allowedTools to reading. Such a workflow does exactly what is needed for review and nothing beyond.
Permissions are granted strictly for the required behavior. Commenting on an issue may need issues write, and commit and push contents write; granting write "just in case" is not allowed. A separate caution is fork PRs: GitHub has its own secret and security model for forks, and running privileged untrusted code without a separate design is dangerous. Quick setup is available via /install-github-app, but a production workflow is still reviewed as code, and for Bedrock and Google Cloud OIDC is preferred over long-lived keys.
GitLab is integrated similarly but by its own means. The official integration uses a CI job, Claude Code and the GitLab MCP server, and for production the documentation recommends manual setup. Variables store API or cloud credentials and GitLab access, and job rules limit the pipeline's sources and branches. It helps to see a minimal read-only example once: a job on a merge request, installing Claude Code, a bare run with dontAsk and a narrow allowlist, the result into an artifact.
It is important to understand the boundaries of such a minimal example. A read-only job by itself does not post a comment - it only prepares the result. For full interaction with GitLab you follow the official MCP server setup and grant only the necessary API scopes; write tools like mcp__gitlab are not added until the job really must comment or create a merge request. This is the same least-privilege principle: read-only and an artifact first, and write rights only for a proven need.
A general CI security checklist gathers it all into one check. The source of the action, image and install is pinned or updated in a controlled way; secrets only in the CI secret store; OIDC instead of a long-lived cloud key where possible; the trigger does not give untrusted fork code access to privileged secrets; the contents, issues and PR permissions are minimal; allowedTools, the mode, the sandbox and the network are limited; turn, budget, timeout and concurrency limits are set; the output and artifact contain no secrets and have retention; before merge and deploy - a human review.
It helps to frame this checklist explicitly once and go through it on every CI-config change. Below it is. Each point closes its class of risk: a pinned source - a dependency substitution, minimal permissions - excess access, OIDC - a long-lived key leak, limits - uncontrolled spend, human review - blind trust in an autonomous run. CI is the place where a rights error is costly, because it repeats on every run automatically.
The typical CI failures are predictable and costly. Granting contents write "just in case" and letting the agent push to a protected branch. Running a privileged workflow on a fork PR with no separate security model. Hardcoding the key instead of GitHub Secrets or OIDC. Adding MCP write tools before they are really needed. And not setting turn, budget and timeout limits, letting the spend loose. Grant the minimally necessary rights, keep secrets in the CI store and keep a human at the merge - in CI the cost of a mistake is multiplied by the number of runs.
name: Claude review
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read # minimum for review
pull-requests: write
concurrency:
group: claude-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with: { persist-credentials: false }
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "Review this pull request. Do not modify files."
claude_args: "--max-turns 8 --allowedTools Read,Grep,Glob"# CI security checklist
[ ] action/image/install source pinned or updated in a controlled way
[ ] secrets only in the CI secret store; OIDC instead of a long-lived key
[ ] the trigger gives no privileged secret to untrusted fork code
[ ] contents/issues/PR permissions are minimal
[ ] allowedTools, mode, sandbox and network are limited
[ ] max turns, budget, timeout, concurrency are set
[ ] output/artifact without secrets and with retention
[ ] human review before merge/deploy