Issue 00 · Autumn 2026日本語 · RSS
AgentKoji

Claude Code Headless Mode: claude -p for Scripts and Cron

claude -p exits 0 on success and non-zero on failure. Output formats, passing permissions, resuming sessions, catching errors, and running it from cron.

TL;DR

  • claude -p runs a prompt once, prints the result and exits, with no permission dialogs; piped stdin is capped at 10MB.
  • Output formats are text (default), json (result, session ID and metadata) and stream-json; total_cost_usd is a local estimate.
  • Calls needing approval are denied unless allowed: list tools with --allowedTools (mind the space in Bash(git diff *)) or set --permission-mode.

claude -p runs a prompt once, prints the result, and exits. Unlike an interactive session, it shows no permission dialogs and never waits for a person. This guide covers the basic form, output formats, passing permissions, resuming a conversation, exit codes and error handling, scheduled runs from cron, and a note on pricing. In our test environment (macOS, Claude Code 2.1.304, on 2026-10-20) we checked claude --help and ran claude -p exactly once in an empty directory. On 2026-10-21 we rechecked claude --help with 2.1.305 and did not run claude -p again; the output below is from the 2026-10-20 run.

The basic form

Pass the prompt as an argument, or pipe it in on stdin:

claude -p "What does the auth module do?"
cat build-error.txt | claude -p "concisely explain the root cause of this build error" > output.txt

Piped stdin is capped at 10MB. Past that, Claude Code exits with an error and a non-zero status. For anything larger, write it to a file and reference the path in your prompt.

Pick an output format with --output-format:

ValueWhat you get
textThe default. Just the response text
jsonJSON with the result, the session ID and metadata
stream-jsonNewline-delimited JSON events. The last line is the result

Source: https://code.claude.com/docs/en/headless (checked 2026-10-21)

In an empty directory we ran “Reply with just OK” with --output-format json --no-session-persistence. It exited 0. The main fields looked like this (session ID and cost masked):

{
  "type": "result",
  "subtype": "success",
  "is_error": false,
  "result": "OK",
  "num_turns": 1,
  "session_id": "<masked>",
  "total_cost_usd": <masked>,
  "permission_denials": [],
  ...
}

Pull out the text with jq -r '.result'. total_cost_usd is an estimate Claude Code calculates locally, and it may not match your bill.

claude -p "Summarize this project" --output-format json | jq -r '.result'

Passing permissions

With -p there are no permission dialogs. A call that needs approval is denied unless something approves it. From v2.1.302, a run with no permission mode set starts in Manual (default). List the tools you want to allow with --allowedTools, using the same syntax as permission rules:

claude -p "Look at my staged changes and create an appropriate commit" \
  --allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"

The space before * in Bash(git diff *) matters. Without it, the rule also matches git diff-index. Instead of listing tools, you can set a baseline with --permission-mode. In claude --help the values are acceptEdits, auto, bypassPermissions, manual, dontAsk, and plan. For locked-down unattended runs such as CI, dontAsk fits: it denies everything that would otherwise prompt.

--dangerously-skip-permissions is the same as --permission-mode bypassPermissions and skips prompts. --help recommends it only for sandboxes with no internet access. Don’t use it outside a container or VM you can afford to break.

Sources: https://code.claude.com/docs/en/headless and https://code.claude.com/docs/en/cli-reference (checked 2026-10-21)

One more catch. Without --bare, -p loads hooks from the project’s .claude/settings.json and servers from .mcp.json, just like an interactive session, and it does so without a trust prompt even in a folder you’ve never trusted. The docs recommend --bare for scripted calls. --bare doesn’t use your subscription login, so it needs ANTHROPIC_API_KEY. For rule syntax, see Claude Code settings.json Permissions: A Cheat Sheet.

Resuming a conversation

--continue picks up the most recent conversation, and --resume <session_id> picks up a specific one:

session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"

There are options for limits, too. --max-turns caps the number of agentic turns and exits with an error when it’s reached. --max-budget-usd stops once the estimated spend reaches the amount you set. Both work only with -p. --max-turns is in the docs but doesn’t appear in claude --help (we checked 2.1.304 and 2.1.305). --no-session-persistence doesn’t save the conversation, so it can’t be resumed later.

Sources: https://code.claude.com/docs/en/headless and https://code.claude.com/docs/en/cli-reference (checked 2026-10-21)

Exit codes and error handling

Per the docs, claude -p exits 0 on success and non-zero when the run fails. An invalid flag is reported on stderr before the run starts. A failure inside the run, such as missing authentication, is printed as the result on stdout. In json output, hitting --max-turns gives the subtype error_max_turns.

Sources: https://code.claude.com/docs/en/headless and https://code.claude.com/docs/en/agent-sdk/agent-loop (checked 2026-10-21)

In the shell, check three things: the exit code, is_error, and an empty result. This script summarizes yesterday’s commits in a dummy todo app:

#!/usr/bin/env bash
# nightly-summary.sh: summarize yesterday's commits of the todo app
set -euo pipefail
cd "$HOME/todo-app"
out=$("${CLAUDE_BIN:-claude}" -p "Summarize the commits from the last 24 hours in 5 bullet points." \
  --output-format json --max-turns 5 \
  --allowedTools "Bash(git log *)" --permission-mode dontAsk)
if [ "$(jq -r '.is_error' <<<"$out")" != "false" ]; then
  echo "claude reported an error: $(jq -r '.subtype' <<<"$out")" >&2
  exit 1
fi
result=$(jq -r '.result // empty' <<<"$out")
if [ -z "$result" ]; then
  echo "empty result" >&2
  exit 1
fi
printf '%s\n' "$result" > "$HOME/todo-app/reports/$(date +%F).md"

We tested the branches by pointing CLAUDE_BIN at stub scripts that return fixed JSON. The real claude wasn’t called.

What the stub returnedOutputExit
is_error: false with a resultWrites the report0
is_error: false, empty resultempty result1
is_error: true, subtype: error_max_turnsclaude reported an error: error_max_turns1
An error on stderr and exit 1Error: unknown option1

Scheduled runs from cron

For anything that runs on a schedule, put it on a small Linux VPS rather than your laptop. Here is the crontab line. It assumes a Linux VPS. Our test environment (macOS) has no flock, so we didn’t run it (not run).

# crontab -e
PATH=/home/dev/.local/bin:/usr/local/bin:/usr/bin:/bin
0 6 * * * /usr/bin/flock -n /tmp/nightly-summary.lock /home/dev/bin/nightly-summary.sh >> /home/dev/logs/nightly-summary.log 2>&1

For environment variables that don’t reach cron, see Claude Code Env Vars Not Working. For why an unattended run shouldn’t hold keys and untrusted input together, see Permission Design for AI Agents.

A note on pricing

According to Anthropic’s help center, the Agent SDK and claude -p still draw from your subscription limits as of 2026-10-21. Previously announced changes to Agent SDK usage were paused on June 15, 2026. An October 7 update added monthly API credits to the Max and Team plans, and those credits also cover the Agent SDK and claude -p. Pricing can change, so check the official page before you subscribe.

Source: https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan (checked 2026-10-21)

For running it in CI, see Running Claude Code in CI: Costs and Traps. Plan prices are in What Claude Code Really Costs.

Summary