5 min read

Claude Code Headless Mode for Batch Tasks and CI/CD

Run Claude Code without interaction for automation, scripts, and pipelines.

Headless mode runs Claude Code without human interaction. Essential for automation, CI/CD pipelines, and batch processing.

What Is Headless Mode

Normal mode: Interactive conversation Headless mode: Single command, automatic execution, exit when done

# Normal (interactive)
claude

# Headless (automated)
claude --headless "Fix all TypeScript errors in src/"

Basic Usage

Single command

claude --headless "Add error handling to all API routes"

With auto-accept

claude --headless --auto-accept "Run tests and fix any failures"

Without --auto-accept, Claude will pause at permission prompts (not useful in automation).

Capture output

claude --headless "Review this code" > review.txt

CI/CD Integration

GitHub Actions

name: AI Code Review
on: [pull_request]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - 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: |
          claude --headless --auto-accept \
            "Review the changes in this PR and post a summary"

GitLab CI

ai-review:
  stage: review
  script:
    - npm install -g @anthropic-ai/claude-code
    - claude --headless --auto-accept "Review the merge request changes"
  only:
    - merge_requests

Pre-commit Hook

#!/bin/bash
# .git/hooks/pre-commit

# Run Claude to check for issues
RESULT=$(claude --headless "Check staged files for obvious bugs or security issues")

if echo "$RESULT" | grep -q "ISSUE FOUND"; then
    echo "$RESULT"
    exit 1
fi

Automation Scripts

Nightly code cleanup

#!/bin/bash
# scripts/nightly-cleanup.sh

claude --headless --auto-accept "
1. Remove unused imports across the codebase
2. Fix any linting errors
3. Update outdated type definitions
4. Commit changes with message 'chore: nightly cleanup'
"

Documentation generator

#!/bin/bash
# scripts/update-docs.sh

claude --headless --auto-accept "
1. Read all public API functions in src/lib/
2. Update docs/api.md with current signatures and descriptions
3. Commit if changes were made
"

Test fixer

#!/bin/bash
# scripts/fix-tests.sh

npm test 2>&1 | tee /tmp/test-output.txt
EXIT_CODE=$?

if [ $EXIT_CODE -ne 0 ]; then
    claude --headless --auto-accept \
        "Tests failed. Output: $(cat /tmp/test-output.txt). Fix the failing tests."
fi

Batch Processing

Process multiple files

#!/bin/bash
# Add documentation to all components

for file in src/components/*.tsx; do
    claude --headless --auto-accept \
        "Add JSDoc comments to all exported functions in $file"
done

Bulk migrations

#!/bin/bash
# Migrate all class components to functional

for file in $(grep -l "extends React.Component" src/**/*.tsx); do
    claude --headless --auto-accept \
        "Convert $file from class component to functional component with hooks"
done

Error Handling

Check exit codes

claude --headless "Fix the bug"
EXIT_CODE=$?

if [ $EXIT_CODE -ne 0 ]; then
    echo "Claude failed to complete the task"
    exit 1
fi

Retry logic

#!/bin/bash
MAX_RETRIES=3
RETRY=0

while [ $RETRY -lt $MAX_RETRIES ]; do
    claude --headless --auto-accept "Complete the task" && break
    RETRY=$((RETRY + 1))
    echo "Attempt $RETRY failed, retrying..."
    sleep 10
done

Timeout handling

timeout 300 claude --headless "Long running task" || echo "Task timed out"

Combining with Scripts

Pre-process, then Claude

#!/bin/bash
# Run cheap checks first, then AI

# Fast local checks
npm run lint || exit 1
npm run typecheck || exit 1

# Expensive AI analysis only if basics pass
claude --headless "Deep code review for security issues"

Claude generates, script verifies

#!/bin/bash
# Claude writes, tests validate

claude --headless --auto-accept "Write unit tests for the auth module"

# Verify the tests actually pass
npm test || {
    echo "Generated tests fail"
    git checkout -- tests/  # Revert
    exit 1
}

Output Handling

JSON output for parsing

claude --headless "List all API endpoints as JSON" | jq '.endpoints[]'

Structured reports

claude --headless "
Generate a code quality report with sections:
1. Security issues
2. Performance concerns
3. Code style violations

Format as markdown.
" > reports/quality-$(date +%Y%m%d).md

Environment Variables

Required:

export ANTHROPIC_API_KEY="your-key"

Optional:

export CLAUDE_MODEL="sonnet"  # Default model
export CLAUDE_MAX_TOKENS="4096"  # Max output

Common Pitfalls

Forgetting —auto-accept

# This will hang waiting for input
claude --headless "Do something"  # Bad

# This works
claude --headless --auto-accept "Do something"  # Good

No error handling

# Dangerous in scripts
claude --headless "Fix everything"
deploy.sh  # May deploy broken code

# Better
claude --headless "Fix everything" && npm test && deploy.sh

Unbounded costs

# Could run forever and cost a lot
while true; do
    claude --headless "Improve the code"
done

# Better: bounded iterations
for i in {1..3}; do
    claude --headless "One round of improvements"
done

Security Considerations

Limit permissions in CI

{
  "permissions": {
    "allow": [
      "read:**/*",
      "write:src/**/*",
      "bash:npm test"
    ],
    "deny": [
      "bash:rm *",
      "bash:*--force*",
      "write:.env*"
    ]
  }
}

Audit logs

Log all headless executions:

claude --headless "$TASK" 2>&1 | tee -a /var/log/claude-automation.log

Secrets management

Never put secrets in the command:

# Bad
claude --headless "Use API key sk-12345 to..."

# Good
# Put secrets in environment, reference in CLAUDE.md
export SERVICE_API_KEY="sk-12345"
claude --headless "Use the service API key from environment"

Quick Reference

# Basic headless
claude --headless "task"

# With auto-accept (required for automation)
claude --headless --auto-accept "task"

# Save output
claude --headless "task" > output.txt

# With timeout
timeout 300 claude --headless --auto-accept "task"

# Check success
claude --headless "task" && echo "Success" || echo "Failed"