How to Run Claude Code Headlessly in CI/CD Pipelines
▶ Watch on YouTube & subscribe to The Stack Underflow
Claude Code is the interactive coding assistant you run in a terminal — but running it interactively is only half the story. Pass a single flag, -p, and the entire character of the tool changes: it takes your prompt as a CLI argument, runs the full agent engine to completion, writes its result to stdout, and exits cleanly with a Unix exit code. No shell to keep open, no human to approve mid-run. That shift is what turns Claude Code from a developer tool into a composable pipeline step.
For any team that already runs automated checks on every pull request, this matters. The same reasoning and tool-use machinery that helps you write code interactively can now review diffs, generate changelogs, flag documentation drift, or perform targeted codemods — all gated by your CI configuration rather than a person watching a terminal.
The one-sentence version:
claude -pis a mode switch that hands Claude Code a single prompt, runs the same full agent loop, and returns structured output — making AI review and automation steps drop-in additions to any pipeline.
Interactive vs. Headless: The Two Modes
Interactive mode is the default: you type into a chat loop, Claude reads your CLAUDE.md context file, asks follow-ups, and you iterate. Headless mode (the -p flag, short for --print) flips one thing: the prompt arrives as a command-line argument instead of from a human typing. The agent then runs to completion and exits.
Interactive (default) Headless (-p / --print)
───────────────────── ───────────────────────
Human types prompt Prompt passed as CLI argument
Agent asks follow-ups One prompt → run → exit
Output: chat messages Output: text, json, or stream-json
Gate: the human Gate: your config, allow list, CI policy
Lifecycle: session Lifecycle: process (exits with code 0/1)
The critical thing the video stresses: headless is not a stripped-down mode. CLAUDE.md is loaded. The Read, Grep, and Bash tools all work. Sub-agents and skills work. The engine is identical — only the input/output contract changes.
The Output Formats: text, json, stream-json
The -p flag pairs with --output-format to control what Claude writes to stdout. There are three values (docs.anthropic.com/claude-code, 2025):
| Format | What you get | Best for |
|---|---|---|
text | Plain prose, default | Human-readable scripts or logs |
json | One JSON envelope at exit: result, session_id, metadata | Downstream jq parsing, structured CI artifacts |
stream-json | Newline-delimited JSON events as they happen | Observability, streaming dashboards, long tasks |
For CI use, json is almost always what you want. The envelope lands as a single parseable artifact when the run completes.
claude -p "Summarise the diff and list security concerns"
--output-format json
--max-turns 10
> review.json
--max-turns caps how many agent reasoning steps (tool calls + responses) the run may take before it stops. For a PR review step, 10–20 is a reasonable ceiling. For a pre-commit hook, 5 is enough. For an overnight refactor, you might allow 80–120. Without a cap, a malformed prompt or unexpected repo state can cause a run to spin.
A Real CI Job: PR Review in GitHub Actions
The official Anthropic action — anthropics/claude-code-action@v1 (GA since August 2025) — wraps a headless run and handles all the GitHub plumbing. For a custom raw invocation the pattern is:
# .github/workflows/ai-review.yml
on:
pull_request:
types: [opened, synchronize]
jobs:
claude-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for git diff
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Run AI PR Review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
git diff origin/${{ github.base_ref }}...HEAD > pr.diff
claude -p "Review the attached diff for bugs, policy issues, and security risks.
Output a JSON object with keys: summary, concerns, suggestions." \
--output-format json \
--allowedTools "Read,Grep" \
--max-turns 15 \
> review.json
- name: Post Review Comment
run: |
gh pr comment ${{ github.event.pull_request.number }} \
--body "$(jq -r '.result' review.json)"
env:
GH_TOKEN: ${{ github.token }}
Two things to notice. First, --allowedTools "Read,Grep" scopes the agent to read-only operations — it cannot write files or run shell commands, which is exactly right for a review task. Second, review.json is a plain file that any downstream step can read. The AI step produces a structured artifact; your CI orchestrates what happens next.
Pull Request opened / updated
│
▼
CI: git diff → pr.diff
│
▼
claude -p "review diff"
--output-format json
--allowedTools Read,Grep
--max-turns 15
│
├── CLAUDE.md loaded
├── Reads repo files (scoped)
├── Iterates up to 15 turns
│
▼
review.json
│
▼
gh pr comment (posts .result)
Guard Rails: Putting the Gate in Config
In interactive mode the human is the gate — you approve tool calls, you notice something going sideways, you Ctrl-C if needed. In headless mode that gate disappears. You must replace it with explicit configuration.
| Guard Rail | Flag / Mechanism | Why it matters |
|---|---|---|
| Tool allow list | --allowedTools "Read,Grep" | Prevents writes, shell commands, network calls the task doesn’t need |
| Max turns cap | --max-turns N | Bounds cost and prevents runaway loops on ambiguous prompts |
| Fail closed | Exit code 1 on error | CI treats a failed AI step like any other failed step; don’t swallow errors |
| Structured output | --output-format json | Downstream steps have a predictable schema; parse failures are loud |
| Scoped credentials | Short-lived API key secret | Limit blast radius if the key leaks; rotate regularly |
| Read-only checkout | actions/checkout without push rights | Agent literally cannot push even if it tries |
The allow-list syntax supports prefix matching. Bash(git diff*) permits any command starting with git diff but nothing else. Bash(npm test) permits exactly one command. Use the narrowest scope the task actually needs (docs.anthropic.com/claude-code/allowedtools, 2025).
The Production-Shaped Fallback
Not every environment has claude on its PATH. A runner image might be missing it, a developer might clone the repo without the binary installed, or you may want CI to degrade gracefully rather than fail hard. The idiomatic pattern:
#!/usr/bin/env bash
set -euo pipefail
PROMPT="List all Python functions missing docstrings."
if command -v claude &> /dev/null; then
claude -p "$PROMPT" \
--output-format json \
--allowedTools "Read,Grep" \
--max-turns 10 \
> output.json
else
echo "Claude Code not on PATH — skipping AI step" >&2
echo '{"skipped": true, "result": null}' > output.json
fi
Check for the binary. Run it if present. Write a known-shape stub if not. Every downstream step gets a parseable output.json regardless of whether Claude ran. The AI step is an enhancement, not a hard dependency — your pipeline never breaks because of a missing binary.
Four Sweet Spots for Headless Claude
The video identifies four categories of tasks where claude -p earns its keep:
-
PR triage — Classify incoming pull requests by size, risk, or affected area. Claude reads the diff and emits a structured label; the pipeline applies it. No human needs to skim every PR to route it.
-
Changelog and release note drafts — Feed Claude the commit log between two tags with
git log v1.2.0..v1.3.0. Get back a structured first draft in markdown. A human edits; Claude generates the skeleton. -
Targeted codemods — Rename a config key across 200 files, update an API call signature after a breaking change, or add a missing import pattern. One unattended headless run, scoped tools, reviewable diff.
-
“Is this doc still true?” checks — Point Claude at a documentation page and the source files it describes. Ask it to flag divergence. Most doc-drift goes undetected for months; a nightly headless run surfaces it the same day.
The common thread: anywhere a developer would normally skim and make a judgment call, claude -p produces a structured signal that the pipeline can act on, log, or route to a human reviewer.
A Note on Billing (June 2026)
Anthropic announced a plan effective June 15, 2026 to move claude -p and Agent SDK usage onto a separate credit pool (separate from interactive subscription limits). As of June 16, 2026 that change was paused — Anthropic stated they are revising the plan and will give advance notice before any future change takes effect. For now, claude -p on subscription plans continues to draw from standard usage limits, exactly as before. Check anthropic.com/pricing for the current state before committing a billing model in production (The New Stack, June 2026).
Common Misconceptions
-
“Headless mode is limited — it can’t use real tools.” Wrong. The full tool set available in interactive mode is available headlessly.
--allowedToolsis a safety scope you set, not a capability ceiling imposed by the mode. Claude can still callBash,Read,Grep,Edit, and any MCP tools you’ve configured. -
“
claude -pneeds a special server or separate API key.” No. It uses the same binary and the sameANTHROPIC_API_KEYas interactive mode. The-pflag is a run-mode switch, not a different product. You can also authenticate via Amazon Bedrock or Google Vertex AI with the same flag. -
“Requesting JSON output means writing complex prompts.” Not really. A natural-language instruction like “output a JSON object with keys: summary, concerns, suggestions” is usually enough. Use
--output-format jsonalongside it; the flag produces a structured envelope regardless of how the model renders the innerresultfield. -
“If Claude errors mid-run, the pipeline will hang.” Properly configured —
--max-turnsset,set -euo pipefailin your shell wrapper, CI step marked to fail on non-zero exit — the process exits with code 1 and your CI system handles it exactly like any other failed step. The fallback pattern adds a second safety net.
Frequently Asked Questions
What does the -p flag actually do under the hood?
It puts Claude Code into “print” mode (the long form is --print). Claude receives the prompt from the argument rather than an interactive session, runs the full agent loop — including tool calls and multi-step reasoning — to completion, writes to stdout, and exits. The process lifecycle is a normal Unix process: it exits with code 0 on success and non-zero on failure.
How do I control which tools Claude can use in CI?
Use --allowedTools with an explicit comma-separated list of permitted tools. For read-only tasks: --allowedTools "Read,Grep". For tasks that may edit files: --allowedTools "Read,Grep,Edit". The flag supports prefix matching — Bash(git log*) allows only git log commands. Anything not on the list is blocked without prompting. (docs.anthropic.com/claude-code, 2025)
Can CLAUDE.md configure headless behavior differently from interactive?
Yes. CLAUDE.md is loaded in both modes, so you can add a CI-specific section. Some teams include a note such as “CI runs: read-only, no file writes” to orient both Claude and human maintainers. You can also use --bare to skip CLAUDE.md loading entirely if you want the headless run to operate on a completely clean context.
What model does claude -p use by default?
It uses the model configured in your Claude Code settings, which defaults to the current recommended model (as of mid-2026, claude-sonnet-4-5 or later in the Sonnet 4.x family). Override it with --model claude-opus-4-5 (or the specific model ID) to use a more capable or more economical model for a given task. Pin the model ID in CI to avoid unexpected behavior after a default change.
What happens if the AI step fails — does it block the whole pipeline?
Only if you configure it to. A failed headless run exits with a non-zero code. Whether that blocks a merge is a branch protection policy decision in your CI config, not something Claude forces. The production-shaped fallback (if command -v claude) and a known-shape stub output ensure the pipeline keeps running even when Claude is unavailable. A failed AI review can be advisory rather than blocking.
Does the official Anthropic GitHub Action use claude -p internally?
anthropics/claude-code-action@v1 (GA, August 2025) wraps a headless run and handles GitHub-specific plumbing: reading PR context, posting comments, and managing permissions. You pass claude_args to forward flags like --max-turns and --allowedTools. For teams that want finer control over the invocation or need to run on non-GitHub CI systems, calling claude -p directly gives full control of the pipeline shape.
Where This Fits in the Series
This tutorial closes out Layer 4 of the Claude Stack — the Surfaces layer — by showing that the same engine powering your interactive sessions can be scripted and dropped into automation with minimal ceremony. To get the most out of it, read How Claude Code Works: The Agent Loop first (that explains what the engine is doing during a headless run), and Claude Code Hooks Explained if you want to add pre- and post-tool gates to your headless runs. The Claude Code Skills, Subagents, and Hooks overview shows how headless runs compose with the broader Claude Code feature set. From here the series moves to making Claude reliable in production — start with Prompt Caching: Cut Your AI Bill and Context Engineering: Pin, Summarize, Prune, Compact. Browse all tutorials for the full series.
Found this useful? The deep version lives on YouTube — new breakdowns of how AI dev tools actually work, weekly.
Subscribe on YouTube →