Level 6: Systems Builder — Headless Automation and Pipelines
Claude Code can run not just interactively in the terminal, but in a “headless mode” using the --print flag — where it receives output and exits. This makes it possible to embed it in shell scripts and CI/CD pipelines like GitHub Actions.
Target audience: Anyone who has practiced multi-step work with Claude Code and wants to try embedding it in an automation pipeline.
Estimated learning time: Read 25min + Practice 40min
What Is Headless Mode?
Section titled “What Is Headless Mode?”Normal Claude Code runs interactively. The --print flag switches it to headless mode, where it accepts a prompt, returns output, and then exits.
# Normal mode (interactive)
claude
# Headless mode (pass a prompt, receive output, exit)
claude --print "Please review the code in src/api/main.py"Headless mode is suitable for calling from scripts or CI, and for piping output to other commands.
Passing Code via Standard Input
Section titled “Passing Code via Standard Input”You can use cat and pipes to pass file contents to Claude.
# Pass a diff via standard input
git diff main...HEAD | claude --print "Please review this diff"
# Pass the contents of multiple files
cat src/api/routes.py | claude --print "Please list the problems in this code"This approach lets you build pipelines that pass dynamically generated data directly to Claude.
Combining —output-format json with jq
Section titled “Combining —output-format json with jq”Using --output-format json makes Claude return output in JSON format. Combined with the jq command, you can handle the response as structured data in subsequent processing.
# Receive output in JSON format
RESULT=$(claude --print --output-format json "
Please analyze the following error log and return the cause and solution in JSON format.
{\"error\": \"cause\", \"solution\": \"solution\"}
$(cat logs/app.log | tail -50)
")
# Extract a specific field with jq
echo "$RESULT" | jq -r '.solution'Embedding in a Shell Script
Section titled “Embedding in a Shell Script”Here’s a general-purpose PR review script. It’s designed to accept the target file extensions as an argument so it can be used in any project.
#!/bin/bash
# scripts/review_pr.sh
# A general-purpose script that has Claude review a PR diff
# Usage: ./scripts/review_pr.sh "*.py *.ts"
set -e
# Target extensions (default: Python and TypeScript)
FILE_PATTERNS=${1:-"*.py *.ts *.tsx"}
# Get the diff against main
DIFF=$(git diff main...HEAD -- $FILE_PATTERNS)
if [ -z "$DIFF" ]; then
echo "No changes to review"
exit 0
fi
echo "=== AI Code Review ==="
REVIEW=$(echo "$DIFF" | claude --print "
The following is a code diff. Please review it from these perspectives:
1. Potential bugs or logic errors
2. Security issues (unsanitized values, hardcoded secrets)
3. Performance issues
4. Readability and maintainability issues
If there are issues, list them in the format \"[Severity: High/Medium/Low] filename:line: description.\"
If there are none, just respond with \"LGTM\".
")
echo "$REVIEW"
# Return exit code 1 if a high-priority issue is detected
if echo "$REVIEW" | grep -q "\[Severity: High\]"; then
echo ""
echo "High-priority issues detected. Please fix them and re-review."
exit 1
fiGrant the script execution permission and test it.
chmod +x scripts/review_pr.sh
./scripts/review_pr.sh "*.py"Embedding in GitHub Actions
Section titled “Embedding in GitHub Actions”Create .github/workflows/ai-review.yml to automatically run a review every time a PR is created or updated.
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Run AI Review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
DIFF=$(git diff origin/main...HEAD)
if [ -z "$DIFF" ]; then
echo "No review targets"
exit 0
fi
echo "$DIFF" | claude --print \
--dangerously-skip-permissions \
"Please review this diff.
Check from the perspectives of bugs, security, and performance.
If there are issues, list them with severity. If not, respond with LGTM." \
> review_result.txt
cat review_result.txt
- name: Post Review Comment
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs')
const review = fs.readFileSync('review_result.txt', 'utf8')
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## AI Code Review\n\n${review}\n\n---\n*Automated review by Claude Code*`
})The —dangerously-skip-permissions Flag
Section titled “The —dangerously-skip-permissions Flag”In automated execution environments like CI where user confirmation is not possible, the --dangerously-skip-permissions flag skips confirmation dialogs.
claude --print --dangerously-skip-permissions "..."This flag grants Claude all operation permissions including file changes and deletions. Use it only in trusted CI environments or other controlled settings where operations are limited and managed. I recommend avoiding routine use in local development environments.
Q. When should I use headless mode vs. normal mode?
Use headless mode for automation scripts, CI/CD, and piping with other commands. Normal mode is better suited for complex interactive tasks or interactive debugging.
Q. What changes when I use —output-format json?
Claude’s response is returned in JSON format including metadata. You can extract specific fields with jq, making it easier to process in subsequent scripts.
Hands-On Tutorial
Section titled “Hands-On Tutorial”Hands-on tutorial for this level →
Next Level
Section titled “Next Level”I’ve understood how to automate PR reviews in headless mode. Next, I’ll learn how to control a browser using MCP.
Let’s move on to Level 7: Browser Automator — Browser Control, Scraping, and PDFs.