Skip to main content

Headless Automation Mode

Headless mode runs Claude Code directly from a script or pipeline with no interactive conversation. Use it for CI/CD, batch jobs, code generation automation, and similar tasks.

Official name change

Anthropic's official documentation has rebranded this feature as the Agent SDK CLI. The -p flag and all CLI options work exactly the same.

The --print flag​

With the --print (-p) flag, Claude Code handles a single request, prints the result to stdout, and exits.

# Basic usage
claude --print "Analyze the complexity of this function" < src/complex.ts

# Pipe file contents in
cat error.log | claude --print "Analyze the cause of this error"

# Save the result to a file
claude --print "Write the API specification" > docs/api.md

Auto-approving permissions​

In a headless environment there is no one to approve prompts, so permissions must be configured up front:

# Auto-approve all permissions (use only in an isolated CI environment)
claude --dangerously-skip-permissions --print "Fix the code"

Headless prompt blocking (--permission-prompts none, v2.1.259+)​

On a fully headless CI/CD host or background script, combine with --permission-prompts none:

claude -p "Analyze the code and write a report" --permission-prompts none

As soon as a tool call requires user input, it is denied immediately with no waiting. Auto modes or preconfigured allow rules keep working as before, so the session does not deadlock while safe behavior stays enabled.

Or auto-approve only specific tools via settings.json:

{
"permissions": {
"allow": ["Read(*)", "Write(*)", "Edit(*)", "Bash(npm test)"],
"deny": ["Bash(rm *)"]
}
}

Headless session supported commands (v2.1.260+)​

Several text commands are also available in -p, Claude Code Desktop, and Agent SDK-based sessions:

  • /reload-plugins: reload plugins mid-session
  • /advisor text commands: set or unset the model advisor without the interactive UI β€” /advisor, /advisor <model>, and /advisor off

Practical automation scripts​

Batch file processing​

Process many files automatically:

#!/bin/bash
# Add JSDoc to every TypeScript file

for file in src/**/*.ts; do
echo "Processing: $file"
claude --print "Add JSDoc comments to the public functions and classes in this file.
Do not change any existing code logic β€” add comments only." < "$file" > "${file}.tmp"

# Verify the result before replacing
if [ -s "${file}.tmp" ]; then
mv "${file}.tmp" "$file"
else
rm "${file}.tmp"
echo "Warning: failed to process $file"
fi
done

echo "Done!"

Generating a code review report automatically​

#!/bin/bash
# Generate a PR review report

BRANCH=${1:-HEAD}
BASE=${2:-main}

DIFF=$(git diff $BASE...$BRANCH)

if [ -z "$DIFF" ]; then
echo "No changes"
exit 0
fi

echo "$DIFF" | claude --print "Review this set of code changes.
Write a markdown report in the following format:
## Summary
## Key Changes
## Potential Issues
## Suggested Improvements
## Verdict (Approve / Request changes)" > review-report.md

echo "Review report generated: review-report.md"

Automated migration​

#!/bin/bash
# API v1 β†’ v2 migration

FILES=$(grep -rl "api/v1" src/)

for file in $FILES; do
echo "Migrating: $file"
cat "$file" | claude --print "Update the 'api/v1' endpoints in this file to 'api/v2'.
Change only the endpoint paths and keep the logic as is.
Output only the full modified file contents (no explanation)." > "$file.migrated"

mv "$file.migrated" "$file"
done

Output format (--output-format)​

The --output-format flag controls the response format:

FormatDescriptionUse case
textPlain text (default)Human reading
jsonStructured JSON (includes session ID and metadata)Parsing in scripts
stream-jsonNewline-delimited JSON streamingReal-time processing
# Get the result as JSON
claude -p "Summarize the project" --output-format json

# Extract just the result text with jq
claude -p "Summarize the project" --output-format json | jq -r '.result'

Structured output with a JSON schema​

You can force a specific output shape with --json-schema:

# Extract function names as an array
claude -p "Extract the function names from auth.py" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'

# Structured output is returned in the .structured_output field
claude -p "Analyze the code" --output-format json --json-schema '...' | jq '.structured_output'

Real-time streaming​

# Receive tokens in real time as they are generated
claude -p "Explain recursion" --output-format stream-json --verbose

# Print only the text in real time
claude -p "Write a poem" --output-format stream-json --verbose --include-partial-messages | \
jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'

Continuing a conversation (--continue, --resume)​

Multi-turn conversations work in headless mode too:

# First request
claude -p "Review the performance problems in this codebase"

# Continue the most recent conversation
claude -p "Focus on the database queries" --continue
claude -p "Summarize the issues you found" --continue

To manage several conversations at once, use session IDs:

# Capture the session ID
session_id=$(claude -p "Start the review" --output-format json | jq -r '.session_id')

# Resume a specific session
claude -p "Continue the review" --resume "$session_id"

Auto-approving tools (--allowedTools)​

Auto-approve specific tools without prompting:

# Allow file reads/edits and Bash
claude -p "Run the tests and fix the failures" \
--allowedTools "Bash,Read,Edit"

# Create a commit (allow git commands only)
claude -p "Create an appropriate commit from the staged changes" \
--allowedTools "Bash(git diff *),Bash(git log *),Bash(git status *),Bash(git commit *)"
Pattern matching with a trailing *

Bash(git diff *) allows every command starting with git diff. The space before the * matters β€” without it, other commands like git diff-index would match too.

Customizing the system prompt​

# Append to the existing system prompt
gh pr diff "$1" | claude -p \
--append-system-prompt "Review for vulnerabilities from a security engineer's perspective." \
--output-format json

# Replace the system prompt entirely
claude -p "Analyze the code" \
--system-prompt "You are a performance optimization expert."

Using environment variables​

Inject context into a headless script:

#!/bin/bash
# Inject environment-specific configuration

ENVIRONMENT=${APP_ENV:-development}
DB_TYPE=${DB_TYPE:-postgresql}

claude --print "Adapt this migration script for a $DB_TYPE database
in the $ENVIRONMENT environment." < migration.sql

Error handling and retries​

#!/bin/bash
MAX_RETRIES=3
RETRY_COUNT=0

while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
RESULT=$(claude --print "Analyze the code" < src/main.ts 2>&1)
EXIT_CODE=$?

if [ $EXIT_CODE -eq 0 ]; then
echo "$RESULT"
break
else
RETRY_COUNT=$((RETRY_COUNT + 1))
echo "Failed ($RETRY_COUNT/$MAX_RETRIES): $RESULT"
sleep 5
fi
done

if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then
echo "Maximum retries exceeded"
exit 1
fi

Make / task runner integration​

Add Claude tasks to a Makefile:

# Makefile

review:
@echo "Running code review..."
@git diff main...HEAD | claude --print "Review this code" > review.md
@echo "Review complete: review.md"

docs:
@find src -name "*.ts" -exec sh -c \
'claude --print "Generate API documentation for this file" < {} > docs/{}.md' \;

migrate:
@claude --print "Generate a migration for these schema changes" \
< schema.diff > migrations/$$(date +%Y%m%d_%H%M%S)_auto.sql
Headless mode caveats
  • Use --dangerously-skip-permissions only in an isolated environment (Docker, a CI sandbox)
  • Code generated headlessly must always be reviewed by a human before deployment
  • Cost monitoring is essential β€” large batch runs can produce unexpected costs

이 챕터λ₯Ό μ™„λ£Œν•˜μ…¨λ‚˜μš”?

ν•™μŠ΅ 진도λ₯Ό μ²΄ν¬ν•˜μ—¬ λ‚˜μ˜ λ‘œλ“œλ§΅ 달성λ₯ μ„ λ†’μ—¬λ³΄μ„Έμš”.