Skip to content

Headless Mode

Estimated time: ~30 minutes

Prerequisite: Phase 10 (Team Collaboration)

Outcome: After this module, you will understand headless mode fundamentals, know how to script Claude Code, and be ready for advanced automation.


You want to run Claude Code as part of a script. Maybe generate tests for all files in a directory. Maybe do code review on every PR automatically. Maybe batch process documentation. But Claude Code is interactive — it waits for your input, shows spinners, expects approval.

Headless mode solves this: Claude executes, outputs result, returns control to your script. No interaction needed. This unlocks automation: cron jobs, CI/CD pipelines, batch processing, and more.


AspectInteractiveHeadless
Invocationclaudeclaude -p "prompt"
InputConversationSingle prompt
OutputFormatted, spinnersRaw stdout
ApprovalRequiredSkipped or auto
Use caseDevelopmentAutomation

The -p (print) flag is the key to headless mode:

Terminal window
claude -p "Your prompt here"
  • Executes the prompt
  • Outputs result to stdout
  • Returns to shell when complete
  • Exit code indicates success/failure
Terminal window
# To variable
result=$(claude -p "Explain this function")
# To file
claude -p "Generate README" > README.md
# Pipe to another command
claude -p "List issues in code" | grep "ERROR"
Terminal window
# Direct prompt
claude -p "Explain recursion"
# With file context
claude -p "Review this code: $(cat file.js)"
# Pipe from stdin ⚠️ Verify support
cat file.js | claude -p "Review this code"
  • 0: Success
  • Non-zero: Error (check stderr)
  • Use in scripts: if claude -p "..."; then ... fi

Scenario: Automate documentation generation using headless Claude Code.

Terminal window
claude -p "What is 2 + 2?"

Output:

4

Check exit code:

Terminal window
echo $?

Output:

0
Terminal window
explanation=$(claude -p "Explain async/await in one paragraph")
echo "$explanation"

Output:

Async/await is a JavaScript feature that makes asynchronous code
easier to write and read by allowing you to write asynchronous
operations in a synchronous-looking style...
Terminal window
claude -p "Generate a README.md for a TypeScript utility library" > README.md
head -5 README.md

Output:

# TypeScript Utility Library
A collection of useful TypeScript utilities for common tasks.
## Installation
generate-docs.sh
#!/bin/bash
for file in src/*.ts; do
echo "Documenting $file..."
doc=$(claude -p "Generate JSDoc comments for: $(cat "$file")")
echo "$doc" > "docs/$(basename "$file" .ts).md"
done
echo "Documentation complete!"

Run the script:

Terminal window
chmod +x generate-docs.sh
./generate-docs.sh

Output:

Documenting src/utils.ts...
Documenting src/helpers.ts...
Documenting src/api.ts...
Documentation complete!
check-security.sh
#!/bin/bash
result=$(claude -p "Review this code for security issues.
Output 'SAFE' if no issues, or 'UNSAFE: [reason]' if issues found.
Code: $(cat "$1")")
if [[ "$result" == SAFE* ]]; then
echo "$1 passed security check"
exit 0
else
echo "$1 failed: $result"
exit 1
fi
Terminal window
if ! output=$(claude -p "Generate tests for $(cat file.js)" 2>&1); then
echo "Error: $output"
exit 1
fi
echo "$output" > tests.js

Goal: Execute your first headless Claude command.

Instructions:

  1. Run: claude -p "Say hello"
  2. Capture to variable: greeting=$(claude -p "Say hello")
  3. Echo the variable: echo "$greeting"
  4. Check exit code: echo $?
💡 Hint

The output should be a simple greeting. Exit code 0 means success.

Goal: Generate code to a file.

Instructions:

  1. Generate a function: claude -p "Write a JavaScript function to capitalize strings" > capitalize.js
  2. Verify: cat capitalize.js
  3. Run: node -e "$(cat capitalize.js); console.log(capitalize('hello'))"
✅ Solution
Terminal window
claude -p "Write a JavaScript function called capitalize that takes a string and returns it with the first letter capitalized" > capitalize.js
cat capitalize.js
node -e "$(cat capitalize.js); console.log(capitalize('hello'))"
# Output: Hello

Goal: Process multiple files with a script.

Instructions:

  1. Create 3 small code files in a test directory
  2. Write a bash script that loops through files
  3. For each file, generate a one-line description
  4. Output all descriptions to summary.txt
✅ Solution
Terminal window
# Create test files
mkdir -p test-batch
echo "function add(a, b) { return a + b; }" > test-batch/math.js
echo "const API_URL = 'https://api.example.com';" > test-batch/config.js
echo "class User { constructor(name) { this.name = name; } }" > test-batch/user.js
# Batch processing script (save as batch-describe.sh)
#!/bin/bash
for file in test-batch/*.js; do
name=$(basename "$file")
desc=$(claude -p "Describe in one line: $(cat "$file")")
echo "$name: $desc" >> test-batch/summary.txt
done
# Run and verify
chmod +x batch-describe.sh
./batch-describe.sh
cat test-batch/summary.txt

Terminal window
claude -p "prompt" # Execute and output
result=$(claude -p "prompt") # Capture to variable
claude -p "prompt" > file.txt # Output to file
claude -p "prompt" | grep "pattern" # Pipe to command
Terminal window
claude -p "Review: $(cat file.js)" # Inline file content
Terminal window
# Error handling
if ! result=$(claude -p "..."); then
echo "Failed"
exit 1
fi
# Loop processing
for f in *.js; do
claude -p "Document: $(cat "$f")" > "${f%.js}.md"
done
CodeMeaning
0Success
Non-zeroError
FlagPurpose
-p "prompt"Headless execution
--helpShow available options

❌ Mistake✅ Correct Approach
Expecting interactive featuresHeadless is one-shot. No conversation.
Long prompts directly in commandUse variables or files for long prompts
Ignoring exit codesAlways check exit codes in scripts
Not escaping special charactersQuote variables: "$(cat file)"
Assuming same behavior as interactiveTest headless separately. Output format differs.
No error handlingCapture stderr, check exit codes
Overloading with huge filesContext limits apply. Chunk large files.

Scenario: Vietnamese startup needed to generate API documentation for 50+ endpoints. Manual process took 2 days per documentation update.

Solution with headless mode:

generate-api-docs.sh
#!/bin/bash
for endpoint in src/routes/*.ts; do
name=$(basename "$endpoint" .ts)
echo "Documenting $name..."
claude -p "Generate OpenAPI documentation for this endpoint:
$(cat "$endpoint")
Output in YAML format." > "docs/api/$name.yaml"
done
# Combine all YAML files
claude -p "Combine these OpenAPI specs into one:
$(cat docs/api/*.yaml)" > docs/openapi.yaml
echo "API documentation generated!"

Results:

  • 2 days manual → 15 minutes automated
  • Runs nightly via cron job
  • Documentation always up-to-date
  • Human review only when needed

Quote: “Headless mode turned Claude from a chat buddy into a documentation factory.”


Next: Module 11.2: SDK Integration