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"
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/advisortext 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:
| 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
μ΄ μ±ν°λ₯Ό μλ£νμ ¨λμ?
νμ΅ μ§λλ₯Ό 체ν¬νμ¬ λμ λ‘λλ§΅ λ¬μ±λ₯ μ λμ¬λ³΄μΈμ.