Architecture·8 min read·10 September 2026

Claude Code Headless Mode CI: Production Pipeline Guide

How to wire claude code headless mode ci into GitHub Actions: -p flag, CLAUDE.md, structured output, and hook enforcement for production pipelines.

By Solomon Udoh · AI Architect & Certification Lead

Claude Code Headless Mode CI: Production Pipeline Guide

Running Claude Code as a scriptable pipeline participant is one of the most consequential configuration decisions an architect makes. Claude code headless mode CI integration turns an interactive coding assistant into a deterministic pipeline step: pass the -p flag, specify a prompt, and Claude Code executes, writes its response to stdout, and exits with a machine-readable exit code. That primitive is small; the production patterns built on top of it are not.

In this post, we cover the flag mechanics, CLAUDE.md authoring for unattended runs, GitHub Actions wiring, hook integration, structured output parsing, and the Domain 3 exam mechanics that surface repeatedly in CCAR-F scenario questions.

What is Claude Code headless mode and how does the -p flag work?

Headless mode runs Claude Code without an interactive terminal session. The -p flag (long form: --print) accepts a prompt string, processes it against the repository, and prints the response to stdout. The process exits with code 0 on success and a non-zero code on any error or tool failure.

bash
claude -p "Review changed files for SQL injection vulnerabilities. Output JSON." \
--output-format json \
--allowedTools "Read,Bash(git diff HEAD~1)" \
--max-turns 5

Three flags compose into the minimal safe CI invocation:

FlagPurposeRecommended value in CI
--output-formatStructures response for machine parsingjson
--allowedToolsRestricts which tools the agent may callComma-separated allowlist
--max-turnsCaps the agentic loop iteration count5 to 10 for review tasks

Without --max-turns, a poorly scoped prompt can trigger an extended agentic loop that exhausts budget and times out the pipeline step. Setting a ceiling is the simplest fail-fast mechanism available.

Why run Claude Code in CI rather than static analysis tools?

Static analysis tools check code against fixed rules. Claude Code applies contextual judgment: it can identify a subtle authentication bypass that no regex covers, or flag a test that mocks the wrong layer. The cost is API tokens and added latency per run.

When we scope invocations narrowly, the trade-off becomes manageable: only changed files, only one concern per invocation, and a low --max-turns ceiling. This keeps per-run cost proportionate and exit latency under 30 seconds for most pull-request contexts.

The Claude Code Configuration & Workflows domain of CCAR-F tests exactly this reasoning: given a scenario, which scoping decision produces a pipeline that is both useful and safe?

How should CLAUDE.md be written for CI agents?

CLAUDE.md is the primary lever for encoding agent behaviour at the repository level. In an interactive session, the agent can ask clarifying questions. In headless mode, it cannot. The CLAUDE.md must answer in advance every question the agent would otherwise raise.

A CI-targeted block covers four concerns: the exact JSON schema the downstream step expects, scope limits on which directories are in-bounds, the precise testing commands that prove a change is safe, and naming conventions the agent should recognise.

markdown
## CI Mode (headless)
When invoked non-interactively:
- Output valid JSON matching `.claude/ci-schema.json`. No prose before or after.
- Do not edit files unless the prompt explicitly requests it.
- Run tests with `npm test -- --passWithNoTests` and include exit code in output.
- Flag any path outside `src/` or `tests/` as out-of-scope and skip it.

Per the three-level configuration hierarchy, project-level CLAUDE.md takes precedence over user-level configuration. In CI, there is no user-level configuration on the runner. Anything that must apply in automated runs lives in the project file, committed to the repository.

This has a direct version control implication: the CI behaviour of the agent is reviewable, diffable, and auditable in the same pull request that changes the prompts or schema.

What does a production GitHub Actions workflow look like?

A minimal read-only review job, safe to run on every pull request:

yaml
name: Claude Code Security Review
on:
pull_request:
types: [opened, synchronize]
jobs:
security-review:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Run headless review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
claude -p "$(cat .claude/prompts/security-review.md)" \
--output-format json \
--allowedTools "Read,Bash(git diff HEAD~1)" \
--max-turns 8 \
> review.json
- name: Gate on high-severity findings
run: |
COUNT=$(jq '[.findings[] | select(.severity == "high")] | length' review.json)
[ "$COUNT" -gt 0 ] && echo "::error::$COUNT high-severity findings" && exit 1 || true
- name: Upload review artefact
if: always()
uses: actions/upload-artifact@v4
with:
name: security-review
path: review.json

The --allowedTools list here grants Read and one scoped Bash subcommand, nothing else. The agent cannot write files, install packages, or call external services. Scoping tools to this level of specificity is the primary safety lever in unattended runs.

How do hooks enforce compliance in headless mode?

Hooks run in headless mode identically to interactive mode. A PostToolUse hook fires on every matching tool call whether a human is watching or not. This makes hooks the correct layer for requirements that must hold unconditionally: stripping credentials from tool results before they enter the context window, asserting that every file write touches only allowed paths, or emitting a structured audit log entry.

json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [{"type": "command", "command": "python3 .claude/hooks/redact_secrets.py"}]
}
]
}
}

The hooks vs prompts decision framework articulates the core distinction: a prompt asks the model to behave correctly; a hook enforces behaviour programmatically regardless of model output. In unattended CI runs, enforcement is the appropriate layer.

For the PostToolUse hooks for data normalisation pattern specifically, the hook receives the tool result before it re-enters the context window. This is the correct interception point for secret redaction: the Bash command has already run, but the output has not yet been appended to conversation history.

What structured output patterns make CI parsing reliable?

Telling Claude Code to output JSON is necessary but not sufficient. The agent may include prose before the JSON block or wrap the object in a markdown fence. Three complementary patterns make parsing deterministic:

PatternMechanismWhen to use
--output-format json flagWraps response in a machine-readable envelopeAll CI invocations
JSON schema in prompt bodyReduces hallucinated or missing fieldsStructured reporting tasks
jq -e assertion in shellFails the step if required keys are absentHard pipeline gates

Embedding a schema in the prompt body acts as a contract the model enforces on its own output, as covered in Prompt Engineering & Structured Output. A minimal example:

json
{
"type": "object",
"required": ["findings", "recommendation"],
"properties": {
"findings": {"type": "array"},
"recommendation": {"type": "string", "enum": ["pass", "fail", "warn"]}
}
}

How does multi-step pipeline design work for large codebases?

A single headless invocation that covers an entire large repository is usually the wrong design. Context grows with the codebase, token cost scales accordingly, and a single failure aborts the entire review.

The better pattern fans the work out per file, then synthesises in a second pass:

bash
#!/usr/bin/env bash
set -euo pipefail
CHANGED=$(git diff --name-only HEAD~1 | grep '\.py$')
for file in $CHANGED; do
claude -p "Review $file for style guide adherence. Output JSON." \
--output-format json --allowedTools "Read" --max-turns 3 \
>> per_file_results.jsonl
done
claude -p "Summarise these per-file results into one overall assessment. Output JSON." \
--output-format json --allowedTools "Read(.claude/)" --max-turns 3 \
< per_file_results.jsonl

Isolate each file's review so context does not bleed across files, then synthesise in a second pass with fresh context. Partial failures are recoverable because each file produces an independent result appended to the JSONL stream. The same logic underpins the per-file and cross-file pass pattern: narrow per-invocation scope so one bad file cannot invalidate the whole pipeline run.

What does the CCAR-F exam test about headless and CI configuration?

Domain 3, Claude Code Configuration and Workflows, carries 20% of the CCAR-F exam weight, equal to Domain 4. All five domain weights per the official exam guide:

DomainTitleWeight
1Agentic Architecture and Orchestration27%
2Tool Design and MCP Integration18%
3Claude Code Configuration and Workflows20%
4Prompt Engineering and Structured Output20%
5Context Management and Reliability15%

Headless mode scenarios surface in Domain 3 items because they test the same reasoning pattern the exam rewards throughout: deterministic over probabilistic, proportionate scope, root-cause tracing. A candidate who understands why --allowedTools is preferable to --dangerouslySkipPermissions in a shared-runner environment, and why hooks are preferable to prompts for compliance enforcement, will answer these items correctly without needing to memorise CLI syntax.

The exam consistently rewards deterministic solutions over probabilistic ones when stakes are high, proportionate fixes, and root-cause tracing.

Anthropic , CCAR-F Exam Guide

Our CCAR-F concept library maps all 30 task statements to atomic concepts. Domain 3 concepts cover the three-level configuration hierarchy, hook implementation patterns, and version control implications of committing CLAUDE.md files to the repository. Practise with scenario-based questions to build the decision-making muscle, not just familiarity with flags.

What safety controls apply in unattended headless runs?

Four controls compose into a layered posture for CI:

  1. --allowedTools allowlist - enumerate the minimum tools needed; anything not listed is refused automatically
  2. --max-turns ceiling - prevents runaway loops; 5 to 10 turns is typical for review tasks
  3. PostToolUse hooks - programmatic enforcement that holds regardless of model output
  4. Scoped API key - a key dedicated to the CI pipeline, separate from developer keys, so usage anomalies appear clearly in the Anthropic console

Avoid --dangerouslySkipPermissions except in fully ephemeral environments where each run starts from a clean container image and the --allowedTools list is already tight. Even there, the allowlist approach is preferable: it documents intent in the invocation itself, survives image updates, and produces a cleaner audit trail.

Frequently asked questions

What flag enables Claude Code headless mode?
The `-p` flag (or `--print`) enables headless mode. Pass it with a prompt string: `claude -p "your prompt"`. Claude Code executes non-interactively, writes the response to stdout, and exits with code 0 on success. Pair it with `--output-format json` and `--allowedTools` for safe, machine-readable CI runs.
How do I parse Claude Code output in a shell script?
Pass `--output-format json` so Claude Code wraps its response in a structured JSON envelope, then pipe the output to `jq`. For pipeline gating, use `jq -e` with a selector expression: if the expected key or value is absent, `jq -e` exits with a non-zero code, which fails the CI step automatically.
Can Claude Code edit files when running headless in CI?
Yes, if `Edit` or `Write` appear in the `--allowedTools` list. Omitting those tools restricts the agent to read-only operations. For review-only jobs, limit the allowlist to `Read` and scoped `Bash` subcommands. Reserve write-capable invocations for auto-fix pipelines that open a follow-up pull request with the changes.
How do I prevent Claude Code from running indefinitely in CI?
Set `--max-turns` to a low ceiling, typically 5 to 10 for review tasks. This caps the number of agentic loop iterations. Also set a step-level timeout in your CI platform (for example, `timeout-minutes: 10` in GitHub Actions) as a second line of defence. Together these two controls bound both iteration count and wall-clock time.
Does CLAUDE.md apply when Claude Code runs headless?
Yes. CLAUDE.md is read at startup regardless of whether the session is interactive or headless. In CI, only the project-level CLAUDE.md applies because the runner has no user-level configuration. Commit all CI-specific instructions to the project-level file so they are version-controlled alongside your pipeline definitions.
How do I secure the Anthropic API key in a CI pipeline?
Store the key as an encrypted secret in your CI platform (for example, GitHub Actions Secrets) and inject it via the `ANTHROPIC_API_KEY` environment variable. Use a key dedicated to the CI pipeline, separate from developer keys, so you can rotate or revoke it independently and see usage anomalies clearly in the Anthropic console.

People also ask

How do you run Claude Code non-interactively?
Pass the `-p` (or `--print`) flag with a prompt string: `claude -p "your prompt"`. Claude Code processes the prompt without an interactive session, prints the response to stdout, and exits. Add `--output-format json`, `--allowedTools`, and `--max-turns` to make the invocation safely scoped and machine-parseable for automated environments.
What is the --print flag in Claude Code?
`--print` (short form: `-p`) switches Claude Code from interactive to headless mode. It accepts a prompt argument, executes it against the repository, prints the response to stdout, and exits with code 0 on success or non-zero on failure. It is the foundation of all Claude Code CI and automation integration patterns.
Can Claude Code be used in GitHub Actions?
Yes. Install with `npm install -g @anthropic-ai/claude-code`, inject `ANTHROPIC_API_KEY` via repository secrets, and invoke `claude -p` in a run step. Set `--allowedTools` to restrict what the agent can access, use `--max-turns` to cap loop iterations, and pipe output to `jq` to gate the pipeline step on specific findings.
How do I get Claude Code output in JSON format in CI?
Add `--output-format json` to any `-p` invocation. Claude Code wraps its response in a structured JSON envelope. Providing a JSON schema in the prompt body further reduces missing or extra fields. Use `jq -e` in a subsequent shell step to assert required keys are present and fail the pipeline if any are absent.
Does running Claude Code in CI pipelines cost money?
Yes. Each headless invocation consumes API tokens billed to your Anthropic account at standard API pricing. Cost per run scales with files read, tools called, and turns taken. Narrow the `--allowedTools` list, set a low `--max-turns` ceiling, and scope prompts to changed files only to keep per-run expenditure proportionate.

About the author

Solomon Udoh

AI Architect & Certification Lead

Solomon Udoh is an AI Architect who designs and ships production agent systems on the Claude API and Claude Code. He built AI Skill Certs' adaptive engine and authored its 174-concept knowledge graph, mapping every Claude Certified Architect - Foundations objective to hands-on, exam-aligned practice.

  • Designs production multi-agent systems on the Claude API and Agent SDK
  • Author of the AI Skill Certs knowledge graph (174 mapped exam concepts)
  • Builds with MCP, Claude Code, structured outputs, and agentic loops daily
  • Reviews every concept page against the official Anthropic exam guide

You might also like

Ready to put it into practice?

Study every exam concept with an adaptive tutor.

Start studying