ailiteracynepal 🇳🇵
Text size

Chapter 04 · Section I · 12 min read

4.1 Headless mode: --output-format, streams, exit codes

How to invoke Claude Code from a script — one-shot prompts with structured output, streaming events, and exit codes you can branch on.

Headless mode is Claude Code in one-shot form: no interactive REPL, no permission prompts, a single prompt goes in and a single result comes out. It is what turns Claude Code into a component you can call from shell scripts, cron jobs, and CI pipelines.

The basic shape

claude -p "summarise the changes in the current branch" \
  --output-format json \
  --permission-mode acceptEdits
  • -p — the prompt. Everything after is one message.
  • --output-format — how the result is printed.
  • --permission-mode — usually acceptEdits or bypassPermissions in headless because there is no human to approve.

Output format: text

The default. Prints Claude’s reply as plain text. Fine for eyeballing.

claude -p "list the top-level directories" > /tmp/dirs.txt

Output format: json

Single JSON object per invocation:

{
  "type": "result",
  "subtype": "success",
  "duration_ms": 1832,
  "num_turns": 3,
  "result": "The changes on this branch are…",
  "total_cost_usd": 0.0142,
  "session_id": "abc123..."
}

Parse with jq:

result=$(claude -p "summarise" --output-format json)
cost=$(echo "$result" | jq -r '.total_cost_usd')
reply=$(echo "$result" | jq -r '.result')

Perfect for scripts that want the reply and the metadata.

Output format: stream-json

Newline-delimited JSON — one event per line as they happen:

{"type":"assistant","message":{...}}
{"type":"user","message":{...}}    // tool result
{"type":"result","subtype":"success","result":"...","total_cost_usd":0.02}

Useful when you want to react to events as they arrive — a progress meter, an incremental sink, a pipeline that consumes each tool call.

claude -p "audit" --output-format stream-json | while read -r line; do
  echo "$line" | jq 'select(.type=="assistant")'
done

Exit codes

Non-zero exits mean something failed:

  • 0 — success. The result.subtype in json output is "success".
  • 1 — general error (bad flag, unreachable model, IO error).
  • 2 — permission denied (a hook blocked, or bypassPermissions refused a denied pattern).
  • 130 — user interrupt (SIGINT).

Branch on the exit code in scripts. Do not parse stderr for control flow.

Check your understanding

Quick check

You want a nightly cron job that runs Claude Code, parses the reply and the cost from the result, and emails a summary. Which `--output-format` is right?