Skip to content
X

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


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.

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.

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'

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
fi

Grant the script execution permission and test it.

chmod +x scripts/review_pr.sh
./scripts/review_pr.sh "*.py"

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*`
            })

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 for this 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.