The Complete Claude Guide
← Back to topic
Chapter 28 · CLI

Headless: non-interactive runs, pipes and JSON

Headless mode runs Claude Code without an interactive interface: a prompt goes in, a result comes out. With pipes, JSON output and streaming it becomes a building block in scripts and automation pipelines.

Verified against source on 2026-09-30

Video

Script it only after it works interactively

You will write a bounded non-interactive call, choose the right output format and keep CI runs free of interactive prompts. Outputs are illustrations.

Headless and Agent SDK sources checked September 30, 2026 describe claude -p for scripts and CI, with --bare for reproducible runs and JSON output formats for machines. A scripted call has no one to ask, so permission planning is the design.

Prove the prompt interactively first

A prompt that works once in a session can fail silently in CI: different working directory, missing context, no human to answer. Run it interactively in the practice repo, then convert it with explicit flags.

Design a non-interactive typo check for the course content files.
Input: the diff or file list supplied by the caller, not broad repo access.
Output: filename:line findings only; empty output means clean.
No edits, no network, no hooks or MCP in the run.
Bare mode for reproducibility

--bare skips auto-discovery of hooks, skills, plugins, MCP servers, auto memory and CLAUDE.md, so the run does not pick up whatever a teammate configured. Authentication then comes from ANTHROPIC_API_KEY or an apiKeyHelper in explicit settings, not OAuth.

git diff main | claude --bare -p "you are a typo linter. for each typo in this diff, report filename:line on one line and the issue on the next. return nothing else." --allowedTools "Read"

Piping the diff means Claude never needs shell access to get the data. Piped stdin is capped at 10MB; larger inputs belong in files referenced by path.

Formats for humans and machines

--output-format json returns metadata including total_cost_usd, so scripted callers can track spend per run. stream-json emits newline-delimited events. Add --json-schema when downstream code expects a shape, and treat the format keyword as annotation, not enforcement.

claude --bare -p "List the chapter titles changed in this diff" --output-format json | jq -r '.result'

Always parse: a script that greps prose output breaks the first time the phrasing changes.

No prompter means no prompts

In -p runs there is no one to approve tool calls: they follow configured rules, and unapproved needs fail or wait without a human. --allowedTools pre-approves a narrow set; keep it minimal. A CI job that needs Write for a lint task is a design smell.

Background Bash tasks started during a -p run are terminated shortly after the final result; background subagents and workflows are waited on, capped by default. A stuck agent cannot hold your CI open forever, but it can waste minutes, so scope the task tightly.

Graded practice

Easy: conversion

Turn an interactive prompt into a -p call. Success: same output, explicit flags.

Intermediate: format

Choose text, json or stream-json for three consumers. Success: machine consumers get structured output.

Challenging: failure mode

The prompt needs a tool that is not allowed. Success: clear failure, not silent wrong answer.

Troubleshooting

Works locally, fails in CI: check bare mode, auth and working directory.

Empty output treated as success: distinguish clean from crashed via exit code.

Over 10MB stdin: reference a file path instead.

Hang on background task: inspect the wait cap and task necessity.

Cost surprise: read total_cost_usd in JSON output per run.

Mechanism animation (illustration)

Filmed demo · Video is added in the media phase

Recap

Comprehension check

Why limit permissions in headless runs?

Recap

Sources and further reading

← Previous Back to topic Next →