The agent in CI: headless mode and automation
Besides interactive mode, Claude Code runs non-interactively (headless). That is intended for scripts and CI:
-p "<prompt>"— a non-interactive run.--output-format json— structured output (result, session ID, usage stats, a cost estimate).--json-schema '<schema>'— constrain the output to a specific JSON schema.--allowedTools "Read,Edit,Bash"— which tools are permitted.--permission-mode dontAsk— anything not on the list is denied.--bare— skips auto-discovery of hooks, skills, plugins and MCP servers.
The exit code is 0 on success and non-zero on failure, so scripts can branch on it.
`--bare` is the recommended mode for CI. The reason is security: without it a -p session runs the hooks from the project's .claude/settings.json and connects to the servers in .mcp.json — with no approval dialog at all. So an untrusted fork's PR could execute code in your CI. --bare reads none of it.
| CI scenario | Suitability | Note |
|---|---|---|
| Automatic review comment on a PR | Good | Keep it non-blocking — noise wears the team down |
| Explaining a build failure | Good | Pipe the log in — no Bash permission needed |
| Issue triage and labelling | Good | Keep write permissions narrow |
| Drafting release notes | Good | Human check before publishing |
| Auto-fixing a failing test | Risky | The agent may change the test to make it pass |
| Automatic merge | No | The human gate must not be removed |
yaml
# GitHub Actions — a non-blocking review comment on a PR
name: AI review
on: pull_request
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Claude Code
run: curl -fsSL https://claude.ai/install.sh | bash
- name: Review diff
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
run: |
git diff origin/${{ github.base_ref }}...HEAD -- '*.kt' > diff.txt
# Pipe the diff in: the agent needs no Bash permission
cat diff.txt | claude --bare -p \
"Review this Android diff. Report ONLY blocking and medium
severity problems: crash risk, resource leaks, forgotten
error cases, broken Compose state hoisting.
Do NOT write style notes. Format: [LEVEL] file:line - problem.
If there are no problems, say so; do not invent findings." \
--output-format json > review.json
- name: Post comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
jq -r '.result' review.json > body.md
gh pr comment ${{ github.event.pull_request.number }} --body-file body.mdThree things to note: `--bare` (security), piping the diff in (no Bash permission needed), and posting the result as a comment (non-blocking).
Security rules for the agent in CI:
- Use `--bare` so hooks and MCP configuration from the repo don't run.
- Least privilege — a narrow
--allowedToolslist, or--permission-mode dontAsk. - Token management — create a long-lived CI token with
claude setup-tokenand store it as theCLAUDE_CODE_OAUTH_TOKENsecret. Note that--baredoes not read that variable, so useANTHROPIC_API_KEYthere. - Beware fork PRs — content from an external fork is untrusted (prompt injection). Either don't run the agent on those, or run it read-only.
- Track cost — the
--output-format jsonoutput carries a per-invocation cost estimate; log it.
📚 Sources and documentation
- Run Claude Code programmaticallyofficialcode.claude.com
`-p`, `--bare`, `--output-format`, `--json-schema` and CI guidance.
- GitHub Actionsofficialcode.claude.com
- CLI referenceofficialcode.claude.com