Claude Academy
intermediate16 min

Headless Mode: Claude in Your Scripts

Learning Objectives

  • Use claude -p for headless (non-interactive) execution
  • Control output format with --output-format
  • Limit agent loops with --max-turns
  • Build automation scripts and CI/CD integrations

Claude Without the Chat

Everything you've done with Claude Code so far has been interactive — you type, Claude responds, you type again. Headless mode removes the interaction. Claude takes input, processes it, and produces output. One shot, no conversation.

This transforms Claude Code from a chat tool into a command-line utility you can embed in scripts, CI/CD pipelines, cron jobs, and unix workflows.

The Basics: claude -p

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

claude -p "What is the current time in UTC?"

Claude processes the prompt, outputs the answer to stdout, and exits. No interactive session opens.

From Stdin

You can also pipe input:

cat src/services/auth.ts | claude -p "Find security vulnerabilities in this code"

Or combine piped input with a prompt:

echo "function add(a, b) { return a + b }" | claude -p "Add TypeScript types to this function"

From Files

Use @file references just like interactive mode:

claude -p "Review @src/services/payment.ts for race conditions"

Output Formats

Plain Text (Default)

claude -p "Explain what src/lib/hmac.ts does"

# Outputs: plain text explanation

JSON

claude -p "Explain what src/lib/hmac.ts does" --output-format json

Returns structured JSON:

{

"type": "result",

"subtype": "success",

"result": "The hmac.ts file implements HMAC signature verification...",

"session_id": "abc123",

"cost_usd": 0.003,

"is_error": false,

"total_turns": 1

}

This is essential for programmatic processing — your script can parse the JSON, check for errors, and extract the result.

Stream JSON

claude -p "Analyze this codebase" --output-format stream-json

Outputs JSON events as they happen, line by line. Each line is a JSON object representing a different event (thinking, tool use, text output). This is useful for real-time monitoring of long-running operations.

Controlling Behavior

--max-turns

Limits tool-use cycles to prevent runaway loops:

claude -p "Fix all lint errors in src/" --max-turns 20

Without --max-turns, Claude might loop indefinitely — fix a file, run lint, find more errors, fix those, run lint again. Setting a limit ensures it stops after a reasonable number of cycles.

Good defaults:

  • Simple tasks (explain, review): --max-turns 5
  • Moderate tasks (fix bugs, write tests): --max-turns 15
  • Complex tasks (refactor, migrate): --max-turns 30

--append-system-prompt

Adds instructions to Claude's system prompt:

claude -p "Review this code" \

--append-system-prompt "You are a security auditor. Focus ONLY on security issues. Ignore style, performance, and readability. Rate each finding as Critical, High, Medium, or Low."

This creates a specialized persona for the task. Claude follows both its default instructions and your appended instructions.

--allowedTools

Restricts which tools Claude can use in headless mode:

claude -p "Analyze the codebase structure" \

--allowedTools "Read" "Bash(find )" "Bash(wc )"

In CI/CD, this is critical for safety. You can ensure headless Claude can only read files and run specific commands — never write files or execute arbitrary commands.

Scripting Patterns

Batch File Analysis

#!/bin/bash

# Analyze all service files for complexity

for file in src/services/*.ts; do

echo "=== Analyzing: $file ==="

claude -p "Analyze the complexity of @$file. Rate as Low/Medium/High \

and suggest simplifications if High." --max-turns 3

echo ""

done

Automated Test Generation

#!/bin/bash

# Generate tests for all untested service files

for file in src/services/*.ts; do

test_file="${file%.ts}.test.ts"

if [ ! -f "$test_file" ]; then

echo "Generating tests for $file..."

claude -p "Generate comprehensive tests for @$file. Follow the testing \

conventions in CLAUDE.md. Output only the test file content." \

--max-turns 10 > "$test_file"

fi

done

Code Quality Report

#!/bin/bash

# Generate a daily code quality report

report="reports/quality-$(date +%Y-%m-%d).md"

{

echo "# Code Quality Report — $(date +%Y-%m-%d)"

echo ""

echo "## Lint Issues"

pnpm lint 2>&1 | claude -p "Summarize these lint results in a table: \

file, issue count, most common issue type" --max-turns 1

echo ""

echo "## Test Coverage"

pnpm test:cov 2>&1 | claude -p "Extract coverage percentages per module. \

Flag any module under 80%." --max-turns 1

echo ""

echo "## Complexity Hotspots"

claude -p "Find the 5 most complex functions in src/services/ based on \

cyclomatic complexity. List them with file, function name, and why they're \

complex." --max-turns 10

} > "$report"

echo "Report generated: $report"

CI/CD Integration

# .github/workflows/claude-review.yml

name: Claude Code Review

on:

pull_request:

branches: [main]

jobs:

review:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: Setup Node

uses: actions/setup-node@v4

with:

node-version: '20'

- name: Install Claude Code

run: npm install -g @anthropic-ai/claude-code

- name: Review PR

env:

ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

run: |

claude -p "Review the changes in this PR for security issues, \

performance problems, and convention violations. Output a \

markdown report." \

--output-format text \

--max-turns 15 \

--allowedTools "Read" "Bash(git diff *)" > review.md

- name: Post Review

uses: actions/github-script@v7

with:

script: |

const fs = require('fs');

const review = fs.readFileSync('review.md', 'utf8');

github.rest.issues.createComment({

issue_number: context.issue.number,

owner: context.repo.owner,

repo: context.repo.repo,

body: review

});

Processing Patterns

JSON Output for Scripts

# Get structured analysis

result=$(claude -p "Count the lines of code in src/" \

--output-format json \

--max-turns 5)

# Parse with jq

total_lines=$(echo "$result" | jq -r '.result' | grep -oP '\d+(?= total)')

echo "Total lines: $total_lines"

Error Handling

#!/bin/bash

result=$(claude -p "Fix the type error in src/services/auth.ts" \

--output-format json \

--max-turns 10 2>&1)

is_error=$(echo "$result" | jq -r '.is_error')

if [ "$is_error" = "true" ]; then

echo "Claude failed to fix the issue"

echo "$result" | jq -r '.result'

exit 1

fi

echo "Fix applied successfully"

Key Takeaway

Headless mode (claude -p) transforms Claude Code from an interactive chat into a scriptable command-line tool. Use --output-format json for programmatic processing, --max-turns to prevent runaway loops, --append-system-prompt for specialized personas, and --allowedTools for CI/CD safety. This unlocks automation: batch file analysis, test generation, code quality reports, and CI/CD integration — all powered by Claude but running without human interaction.