Skip to content

Headless Mode

Headless mode is what happens when you add -p to a claude command. Instead of opening an interactive session, Claude reads your prompt, does its work, writes to stdout, and exits. It is a Unix program.

Terminal window
claude -p "What does the auth module do?"

That is the entire interface. Everything else — pipes, output formats, scripts, CI integration — is standard shell plumbing applied to this primitive.

Note: The flag -p (short for --print). Both -p and --print work. The official docs previously called this “headless mode”; current docs refer to it as the claude CLI (not agent-sdk).


Basic Usage

Terminal window
# Ask a question about your codebase
claude -p "Summarize the purpose of each file in src/"
# Pipe content in
cat build-error.txt | claude -p "Explain the root cause of this error"
# Pipe from a command
git log --oneline -20 | claude -p "Write release notes from these commits"
# Combine with jq for structured output
claude -p "List all exported functions in auth.ts" --output-format json | jq '.result'

Claude reads stdin automatically when it is piped in. You do not need any special flag to enable stdin — just pipe to it.


How It Flows

graph LR A[stdin / file / command] -->|pipe| B[claude -p] B --> C{--output-format} C -->|text| D[plain text → stdout] C -->|json| E[JSON object → stdout] C -->|stream-json| F[newline-delimited JSON → stdout] D --> G[terminal / file / downstream tool] E --> G F --> G style A fill:#1e293b,color:#7dd3fc,stroke:#334155 style B fill:#1e293b,color:#f59e0b,stroke:#334155 style C fill:#1e293b,color:#a78bfa,stroke:#334155 style D fill:#1e293b,color:#86efac,stroke:#334155 style E fill:#1e293b,color:#86efac,stroke:#334155 style F fill:#1e293b,color:#86efac,stroke:#334155 style G fill:#1e293b,color:#7dd3fc,stroke:#334155

Output Formats

Three formats available via --output-format:

text (default)

Plain text. What Claude says, nothing else. Best for human-readable output, writing to files, or piping to tools that expect plain text.

Terminal window
cat error.log | claude -p "Summarize the errors" --output-format text
# → "The log shows 3 distinct errors: a null pointer exception in auth.js line 45,
# a timeout in db.connect(), and a missing environment variable DATABASE_URL."

json

A JSON object with the full result and session metadata. The text answer is in .result.

Terminal window
claude -p "Summarize this project" --output-format json
{
"result": "This is a Next.js application that...",
"session_id": "550e8400-e29b-41d4-a716-446655440000",
"cost_usd": 0.0023,
"usage": {
"input_tokens": 1842,
"output_tokens": 312
}
}

Useful in scripts where you want to capture cost, session ID for --resume, or parse the answer programmatically:

Terminal window
# Extract just the text answer
claude -p "Explain auth.ts" --output-format json | jq -r '.result'
# Capture session ID for follow-up
SESSION=$(claude -p "Review auth.ts" --output-format json | jq -r '.session_id')
claude -p "Now focus on the token refresh logic" --resume "$SESSION"

stream-json

Newline-delimited JSON objects emitted in real time as Claude generates them. Each line is one event. Use --verbose --include-partial-messages to get token-by-token streaming.

Terminal window
claude -p "Explain recursion" \
--output-format stream-json \
--verbose \
--include-partial-messages

To display streaming text in the terminal as it arrives:

Terminal window
claude -p "Write a summary of this codebase" \
--output-format stream-json \
--verbose \
--include-partial-messages | \
jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'

stream-json is for monitoring dashboards, progress indicators, or any case where you want output before the full response is ready.


Unix Pipe Patterns

These patterns work in your terminal, in shell scripts, and in CI pipelines.

Explain a build error:

Terminal window
cat build-error.txt | claude -p "Explain the root cause concisely. What file and line is the actual problem?"

Flag anomalies in logs:

Terminal window
tail -200 app.log | claude -p "Flag any errors, warnings, or anomalies. Format: [SEVERITY] line N — description"

Security review of changed files:

Terminal window
git diff main --name-only | claude -p "Review the changed files listed here for potential security issues. Focus on: SQL injection, XSS, hardcoded secrets, missing auth checks."

Summarize test failures:

Terminal window
npm test 2>&1 | claude -p "Summarize the failing tests and likely root causes. Group by component."

Extract structure from unstructured output:

Terminal window
cat api-response.json | claude -p "Extract all user IDs from this JSON. Return as a plain newline-separated list, nothing else."

Review a diff before committing:

Terminal window
git diff --staged | claude -p "Review my staged changes. Is anything missing? Any bugs? Any security concerns?"

package.json Scripts

Add Claude as an AI-powered linter or utility command in your project:

{
"scripts": {
"ai:review": "git diff --staged | claude -p 'Review staged changes for bugs, missing error handling, and security issues. Be concise.' --output-format text",
"ai:release-notes": "git log --oneline -30 | claude -p 'Write release notes. Group: Features / Bug Fixes / Breaking Changes.' --output-format text",
"ai:explain-error": "cat /tmp/last-error.txt | claude -p 'Explain root cause and suggest fix' --output-format text",
"ai:audit": "claude -p 'Audit src/ for TODO/FIXME comments and security anti-patterns. Write to audit.md.' --allowedTools 'Bash,Read,Write' --permission-mode plan"
}
}

Run with npm run ai:review before every commit, or add it to a pre-commit hook.


--bare for Fast CI Runs

By default, claude -p loads the same startup context as an interactive session: hooks, skills, plugins, MCP servers, CLAUDE.md, and auto-memory. In CI this is usually unnecessary and adds latency.

--bare skips all of that:

Terminal window
claude --bare -p "Summarize this file" --allowedTools "Read"

What --bare skips: hooks, skills, plugins, MCP servers, auto-memory, CLAUDE.md discovery.

What --bare keeps: Bash, Read, Edit, Write tools. Whatever you pass explicitly via flags.

This is the recommended mode for CI scripts. It starts faster and produces identical results on every machine regardless of what is configured locally. The official docs note it will become the default for -p in a future release.


--permission-mode Options

Control what Claude is allowed to do without interactive prompts:

ModeWhat Claude can do
defaultAsks before running Bash or writing files
acceptEditsWrites files without prompting; still asks for Bash
planDescribes what it would do; executes nothing
dontAskDenies anything not in your permissions.allow rules
bypassPermissionsSkips all permission checks (use only in isolated CI environments)

For read-only analysis in CI, --permission-mode plan is the safest choice:

Terminal window
cat codebase-summary.txt | claude -p "Identify security risks" --permission-mode plan

For CI jobs that need to write files (e.g., generating RELEASE_NOTES.md):

Terminal window
claude -p "Write release notes to RELEASE_NOTES.md" \
--allowedTools "Bash,Read,Write" \
--permission-mode acceptEdits

Continuing Conversations

-p sessions can be continued. This lets you chain analysis steps without re-sending all context:

Terminal window
# First pass: broad review
claude -p "Review this codebase for performance issues" --output-format json > first-pass.json
SESSION=$(cat first-pass.json | jq -r '.session_id')
# Second pass: drill into a specific finding
claude -p "Focus on the database query patterns you found. Show the worst three with fixes." \
--resume "$SESSION" \
--output-format text

Or using --continue to resume the most recent conversation in the current directory:

Terminal window
claude -p "Review auth.ts"
claude -p "Now check if the token refresh handles clock skew" --continue

Output Format Comparison

Use caseFormatCommand
Human-readable reporttextclaude -p "..." --output-format text
Parse answer in scriptjson... --output-format json | jq -r '.result'
Show progress in terminalstream-json... --output-format stream-json --verbose --include-partial-messages
Get session ID for resumejson... --output-format json | jq -r '.session_id'
Cost trackingjson... --output-format json | jq '.cost_usd'

Auto-Approving Tools

Without --allowedTools, Claude will pause and ask permission before using tools. In non-interactive mode this hangs the process. Always specify which tools Claude needs:

Terminal window
# Read-only analysis — only needs Read
claude -p "Explain the auth module" --allowedTools "Read"
# Analysis that runs commands — needs Bash and Read
cat package.json | claude -p "Check for known vulnerable packages" --allowedTools "Bash,Read"
# File generation — needs Read and Write
claude -p "Generate API docs from src/ and write to docs/api.md" --allowedTools "Bash,Read,Write"

The --allowedTools flag also accepts permission rule syntax for scoped Bash access:

Terminal window
# Allow only specific git commands
claude -p "Create a commit for staged changes" \
--allowedTools "Bash(git status *),Bash(git diff *),Bash(git commit *),Read"