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.
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"
Or auto-approve only specific tools via settings.json:
{
"permissions": {
"allow": ["Read(*)", "Write(*)", "Edit(*)", "Bash(npm test)"],
"deny": ["Bash(rm *)"]
}
}
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:
| Format | Description | Use case |
|---|---|---|
text | Plain text (default) | Human reading |
json | Structured JSON (includes session ID and metadata) | Parsing in scripts |
stream-json | Newline-delimited JSON streaming | Real-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 *)"
*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
- Use
--dangerously-skip-permissionsonly 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