Run Claude Code headless: pipe a prompt in, get a parseable result out, exit
The bridge from the REPL to automation. One prompt in with <code>claude -p</code>, structured JSON out, no human in the loop โ which means you authorize the tools up front or the run stalls.
Headless mode is Claude Code run as a one-shot command instead of an interactive session: you pass a prompt with claude -p "...", it does the work, prints the result to stdout, and exits. The agent loop, tools, and CLAUDE.md context you already use are all there — just scriptable. That turns Claude into a Unix citizen, something you can drop into a cron job, a git hook, a CI step, or the middle of a shell pipeline.
Two things make this worth wiring up today. As of June 15, 2026, claude -p usage on subscription plans draws from a separate monthly Agent SDK credit rather than your interactive limits, so automated runs stop competing with your editor session — plan the budget accordingly. And the interactive REPL trains a habit that does not survive automation: in headless mode there is no human to approve a tool call, so you decide permissions up front or the run stalls waiting on a prompt no one will answer. Three flags carry most of the weight.
Output format decides what you get back. --output-format text (the default) is the bare answer, fine for echo and pipes. json wraps the result in one object — the answer plus cost, duration, turn count, and a session_id — which is what you want when a script needs to branch on what happened or log spend. stream-json emits each message as a line as it arrives, so a long run is observable instead of a black box. Pair json with jq and you have a programmable agent: ... --output-format json | jq -r '.result'.
Tool authorization is the part the REPL hides from you. --allowedTools pre-approves exactly what Claude may run without asking, and it takes scoped grants: Bash(npm test) permits that one command, not arbitrary shell; Read and Grep alone give you a read-only analyst. --permission-mode sets the posture for everything else — acceptEdits to let file writes through unattended, plan to reason without touching anything. Keep the allow-list as narrow as the job: a headless run that can edit files and run any Bash command is a script that can do anything.
Session control makes multi-step automation safe. --max-turns N caps how many times the agent loops before it stops, bounding both runtime and cost on an unattended job. --continue (-c) resumes the most recent session in the working directory and --resume <id> picks a specific one, so a second claude -p call inherits everything the first read and concluded — a pipeline that analyzes, then acts, without re-feeding context.
The trap is --permission-mode bypassPermissions (and its blunter twin --dangerously-skip-permissions). It removes every guardrail at once, and it looks like the quick fix the moment a run stalls on a permission you forgot to grant. The real fix is a tighter --allowedTools, not a blanket bypass; reserve bypass for a throwaway sandbox you would not mind losing. One more: check the exit code. A headless Claude can finish cleanly having decided it could not do the task, so in CI assert on the output, not just on a zero exit.
The try-it block wires claude -p into a one-line code reviewer you can drop into a git hook.
A headless one-liner that reviews your staged changes and prints a verdict. The diff is inlined via shell substitution, and Claude gets only Read and Grep — nothing that can edit.
git add -A
claude -p "Review the staged diff below for bugs, missing error handling,
and hardcoded secrets. Be concise. End with one line: APPROVE or REQUEST_CHANGES.
$(git diff --cached)" \
--allowedTools "Read,Grep" \
--max-turns 3 \
--output-format text
Want it machine-readable instead? Switch to JSON and pull the verdict with jq:
claude -p "...same prompt..." --output-format json | jq -r '.result'
Drop the first form into .git/hooks/pre-commit and exit 1 when the output contains REQUEST_CHANGES — now every commit gets a second read before it lands.