Skip to content

Run Claude Code headless with the -p flag

Before any of the CI plumbing, you need the one move everything else is built on: running the agent without the chat. The first time you reach for it on the payments service, the goal is small - get the review pass to run from a script instead of from your keyboard. That single capability is what makes a nightly job or a CI check even possible, because both are just this command, fired by something other than you.

Pass -p (or its long form --print) with a prompt and Claude Code runs non-interactively: it does the work, prints the result, and exits. No TUI, no back-and-forth, just a command that returns. This is documented as headless mode, and it’s the foundation of everything in this chapter.

Terminal window
claude -p "Run the payments test suite and summarize any failures" \
--allowedTools "Bash,Read"

That runs to completion and writes its answer to stdout. Because it’s an ordinary command-line program, it composes with everything else a shell can do - which is the point.

Print mode reads stdin, so you can feed it data the way you’d feed any Unix tool. This is the cleanest way to hand the agent exactly the input you want it to look at and nothing else:

Terminal window
git diff main | claude -p "You are a reviewer. Flag any change that looks like a regression in the payments flow. Be terse."

Piping the diff in has a quiet benefit that matters more once permissions get strict: the agent doesn’t need permission to run git diff itself, because you already ran it and handed over the output. The less the unattended agent has to reach for, the smaller its blast radius - a theme that runs straight into the next lesson.

One limit to know: piped stdin is capped (10MB at the time of writing). For anything larger, write it to a file and point the prompt at the path instead of piping it. The cap and current behavior live in the headless docs.

A script that can’t read the answer is useless. The --output-format flag controls how the result comes back, and it takes three values (headless docs):

  • text - the default; plain prose, fine for a human reading a log.
  • json - a structured object with the answer in a result field plus metadata: session_id, cost, usage. This is what you parse in a script.
  • stream-json - newline-delimited JSON events as they happen, for when you want to stream tokens or watch a long run live.

For automation you almost always want json, because it lets the calling script pull out exactly the piece it needs:

Terminal window
git diff main | claude -p "Review this diff for payments regressions. Reply with FOUND or CLEAN and one line of reasoning." \
--output-format json | jq -r '.result'

That jq -r '.result' is the whole trick to making the agent a step in a pipeline rather than a thing you read. The JSON also carries the session_id, which you can capture and pass to --resume to continue the same conversation in a later call - useful when a loop needs the agent to build on its own earlier output.

Task
Output
Posture
claude -p "Review the changes on this branch for regressions. Be terse." \
  --bare --allowedTools "Read,Bash(git diff *)" --permission-mode dontAsk \
  --output-format json
  • postureThe allowlist is the guardrail - tool patterns like Bash(git diff *) scope commands, not just tools. --bare skips auto-discovery of CLAUDE.md, hooks, and MCP servers so CI behaves deterministically, and dontAsk keeps the run from hanging on an approval no one will answer.
  • outputOne structured object with result, session_id, cost, and usage. Capture session_id if a later run should --resume this one.
  • beyondFirst-party CI: anthropics/claude-code-action@v1 wraps all of this in a GitHub Action - pass any of these flags via claude_args.

For a one-off script or CI command, claude -p is the simplest surface. For a longer-lived application, use the official Claude Agent SDK rather than repeatedly shelling out. Its TypeScript and Python APIs expose a programmatic query() loop with options for allowed tools, permission mode, maximum turns, and streamed messages. The SDK is an embeddable package, not a hosted remote executor: your application or CI job still supplies the runtime, credentials, filesystem, and network isolation.

Use the SDK quickstart for the current TypeScript/Python option names, and the GitHub Actions guide for the first-party GitHub integration.

By default claude -p loads the same context an interactive session would - your CLAUDE.md, project hooks, MCP servers, skills, everything in the working directory and ~/.claude. That’s convenient locally and a liability in automation: a teammate’s hook or a stray MCP server can change the result, so the same command gives different answers on different machines. The headless docs describe a --bare mode that skips hooks, skills, plugins, MCP servers, auto memory, and CLAUDE.md; built-in tools and default behavior still remain. Use it when a run must be reproducible, then pass the settings and tools it actually needs explicitly.

You can now run the review from a script and read its verdict programmatically. But notice what we quietly did in every example: we handed the agent --allowedTools up front, or piped data so it wouldn’t need a tool at all. That pre-grant is only one layer: permission mode, settings, deny rules, hooks, MCP, and automatic discovery also affect what a headless run can do. The next lesson combines those layers into a fail-closed posture.