A pull-request review job, message by message
claude -p + diff + flagsRunning without a person: -p
Plain claude starts an interactive session that waits for someone to type. In a pipeline there is no one, so every CI invocation uses -p (long form --print): Claude Code takes the prompt, runs the agent loop to completion, prints the result and exits. It reads stdin, so you can pipe a diff or a log straight in. It exits with code 0 on success and non-zero when the run fails, so the job can branch on the exit status.
# Pipe the PR diff in; get JSON that matches a schema back
gh pr diff "$PR" | claude -p \
"Review this diff for correctness and security bugs only.
Skip style. Report each issue with file, line, severity." \
--output-format json \
--json-schema "$(cat .ci/review-schema.json)" \
--permission-mode dontAsk \
--allowedTools "Read,Grep,Glob" \
--max-turns 15 \
> review.json
# Exit code tells the job whether the run itself succeeded
jq '.structured_output.issues' review.json| Flag | What it does in CI |
|---|---|
-p / --print | Non-interactive: run the prompt to completion and exit |
--output-format json | One JSON object with the text in result, plus session_id, usage and a cost estimate |
--json-schema '<schema>' | With json, returns output matching your schema in structured_output |
--output-format stream-json | Newline-delimited JSON events as the run progresses (pair with --verbose) |
--allowedTools "…" | Pre-approves specific tools or commands, e.g. Bash(npm test *) |
--permission-mode dontAsk | Denies anything that would otherwise prompt — for locked-down jobs |
--append-system-prompt | Adds instructions while keeping Claude Code’s default behaviour |
--max-turns | Caps how many turns the run may take — a cost backstop |
--resume <session_id> | Continues a specific earlier run, e.g. a follow-up pass |
Permissions for an unattended run
In a -p run, the documentation says the built-in starting permission mode is Manual on every plan — but there is nobody to answer a prompt. Decide up front what the job may do. A review job needs to read; a fix-the-tests job needs to edit and run the test command; almost no job needs arbitrary shell access or network calls.
Choosing a permission baseline for a CI job
- Read and report only
dontAsk+ read toolsanything else is denied - Edit files, run tests
acceptEditsplusBash(npm test *) - Many varied actions
autoa classifier reviews each - Skip every checkAvoidisolated sandboxes only
--allowedTools, and keep deny rules for anything it must never touch.dontAsk still lets through what needs no approval in Manual mode — file reads in the working directory and the built-in read-only commands — plus anything your --allowedTools entries or allow rules cover. Everything else is denied rather than left waiting. The --allowedTools syntax matches the permission rules: Bash(git diff *) allows any command starting with git diff, and the space before * matters.
Project context: CLAUDE.md in the pipeline
By default, claude -p loads the same context an interactive session would, including the project’s CLAUDE.md. That is where review criteria, test conventions and “what not to flag” belong: the GitHub Actions and GitLab guides both recommend a root CLAUDE.md for coding standards and review criteria, kept concise because Claude reads it on every run. Because the CI runner checks out the repository, only the committed project file is there — a rule in someone’s ~/.claude/CLAUDE.md never reaches the pipeline (see 3.1).
Independent review: don’t let the author grade its own work
If one job generates code or tests, reviewing them in the same session is weak: the reviewer carries the reasoning that produced the change and tends to agree with it. The best-practices guide notes that a fresh context improves code review because Claude won’t be biased toward code it just wrote. In a pipeline, that means a separate claude -p invocation (or a separate job) for review, fed the diff and the criteria — not a follow-up question in the generating session.
Same-session review versus independent review
Review in the generating session
- Reviewer holds the author’s reasoning
- Tends to confirm its own choices
- Context already full of the build-up
Separate review invocation
- Sees only the diff and the criteria
- Judges the result on its own terms
- Can run as its own job with read-only tools
When a review job runs again after new commits, give it what it needs to avoid noise: the current diff plus the earlier findings, with an instruction to report only new or still-unresolved issues. Otherwise every push re-posts the same comments and developers learn to ignore them.
GitHub Actions and GitLab CI/CD
You can call claude -p from any pipeline, but there are packaged integrations. On GitHub, anthropics/claude-code-action@v1 runs Claude Code in a workflow. Set up the Claude GitHub App and secret with /install-github-app, or do it by hand. The action has two modes, and it picks one from your configuration.
| Interactive mode | Automation mode | |
|---|---|---|
| Triggered by | An @claude mention in an issue or PR comment | Any workflow event, including pull_request and schedule |
| Configured by | No prompt input | A prompt input (plain text or a /skill) |
| Results go to | A comment on the issue or PR | The workflow log, unless the prompt and tools post elsewhere |
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 15 # stop runaway jobs
permissions:
contents: read
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v6 # CLAUDE.md and skills come from the repo
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "/review-pr" # a skill committed in .claude/skills/
claude_args: "--max-turns 10 --allowedTools Read,Grep,Glob"- The
claude_argsinput passes any Claude Code CLI flag —--max-turns,--model,--allowedTools,--mcp-config. - Secrets go in repository or organisation secrets (
ANTHROPIC_API_KEY, orCLAUDE_CODE_OAUTH_TOKENfor a subscription), never in the workflow file. - Who can trigger — by default the triggering user needs write access, and bot actors are rejected unless listed, which stops bots triggering Claude in a loop.
- Costs — each run uses runner minutes and tokens; the docs suggest a concise CLAUDE.md,
--max-turns, workflow timeouts and concurrency limits. - GitLab — Claude Code for GitLab CI/CD is in beta and maintained by GitLab: you add a job to
.gitlab-ci.ymlthat installs Claude Code and runsclaude -p, withANTHROPIC_API_KEYas a masked CI/CD variable and GitLab’stimeoutkeyword to bound the job.
Traps the wrong answers are built from
| Tempting but wrong | Do this instead |
|---|---|
Running claude without -p in a pipeline | Use -p so it runs the prompt to completion and exits. |
| Parsing free-text review output with regexes | Use --output-format json with --json-schema and read structured_output. |
| Granting broad tool access or bypassing permissions in CI | Use dontAsk or acceptEdits with a minimal --allowedTools list. |
| Reviewing generated code in the session that generated it | Run review as a separate invocation with fresh context. |
| Committing an API key into the workflow file | Store it as a CI secret or masked variable, or use OIDC federation. |
Using --bare and expecting CLAUDE.md to apply | Bare mode skips CLAUDE.md; pass standards with --append-system-prompt-file. |
You should now be able to
- Run Claude Code non-interactively with
-pand branch on its exit code. - Produce machine-readable results with
--output-format jsonand--json-schema. - Choose a permission baseline and
--allowedToolslist for an unattended job. - Supply project standards to CI through the committed CLAUDE.md, or explicitly in bare mode.
- Design an independent review step and reduce repeat or low-value findings.
- Configure the GitHub action (
prompt,claude_args, secrets) or a GitLab job.