getting started

Claude Code Headless Mode: Run Claude in CI/CD and Scripts

Quick Answer

Claude Code's headless mode is activated with the --print flag: `claude --print "your prompt"`. Claude runs, executes tools, produces output, and exits with code 0 on success. Combine with --permission-mode bypassPermissions for fully automated CI runs where no human is available to approve tool calls.

Headless mode turns Claude Code from an interactive REPL into a scriptable CLI tool. Pass `--print` followed by your prompt and Claude will complete the task, write output to stdout, and exit. No TUI, no interactive approval prompts (you'll want --permission-mode dontAsk or bypassPermissions), no waiting.

The `--print` flag accepts the prompt as a string argument OR reads from stdin if you pipe to it. Piping is useful when your prompt is dynamic — generated by another script or contains special characters. The pipe form: `echo "Fix all TODO comments in $FILE" | claude --print`.

Exit codes follow Unix conventions: 0 for success, non-zero for failure. This makes headless Claude composable with shell error handling (`set -e`, `&&` chains, `if claude --print ... ; then`). If Claude cannot complete the task (tool error, API error, refusal), it exits non-zero.

In headless mode, all tool outputs go to stderr and the final Claude response goes to stdout. This lets you capture just the response with `OUTPUT=$(claude --print '...')` while tool noise (file reads, bash output) flows to the terminal or gets discarded with `2>/dev/null`.

For CI pipelines, the minimal environment needed is: ANTHROPIC_API_KEY set, Node 18+, claude installed globally, and a checkout of your repository. Claude Code will read your `.claude/settings.json` and CLAUDE.md automatically — meaning your CI Claude has the same project context as your interactive developer sessions.

Token limits and timeouts apply in headless mode just as in interactive mode. For very large tasks (refactoring thousands of files), break the prompt into smaller scoped tasks and chain them in your shell script. Each invocation starts a fresh context, so passing relevant file paths in each prompt is important.

Examples

Basic headless usagebash
# Simple prompt
claude --print "List all TODO comments in src/ with file paths and line numbers"

# Pipe a dynamic prompt
FILE="src/api/users.ts"
echo "Add JSDoc comments to every exported function in $FILE" | claude --print

# Capture output
SUMMARY=$(claude --print "Summarize all changes in the last 5 commits")
echo "$SUMMARY" >> CHANGELOG.md

# Exit code handling
if claude --print "Run tests and report any failures" \
     --permission-mode bypassPermissions; then
  echo "Tests passed"
else
  echo "Claude reported test failures" >&2
  exit 1
fi
GitHub Actions workflow using headless Claudebash
# .github/workflows/claude-pr-review.yml
name: Claude PR Review
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install Claude Code
        run: npm install -g @anthropic-ai/claude-code

      - name: Run Claude PR review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          DIFF=$(git diff origin/main...HEAD -- '*.ts' '*.tsx')
          REVIEW=$(echo "Review this PR diff for bugs, security issues, and missing tests. Be concise.\n\n$DIFF" | \
            claude --print --permission-mode bypassPermissions)
          echo "$REVIEW"
Pipe stdin for complex promptsbash
#!/bin/bash
# generate-tests.sh — generate tests for all untested functions

set -euo pipefail

# Find functions without matching test files
UNTESTED=$(grep -rn 'export function' src/ | \
  grep -v '.test.' | \
  awk -F: '{print $1}' | sort -u)

# Build prompt
PROMPT="Write Vitest unit tests for the exported functions in these files:\n"
for f in $UNTESTED; do
  PROMPT+="- $f\n"
done
PROMPT+="\nCreate test files alongside each source file."

# Run Claude
echo -e "$PROMPT" | claude --print \
  --permission-mode dontAsk \
  2>&1 | tee claude-test-generation.log

Tips

  • Always set ANTHROPIC_API_KEY in your CI secrets — never hardcode it. GitHub Actions: `env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}`.
  • Use `--permission-mode dontAsk` with a tight allow list rather than bypassPermissions when possible — it's safer if Claude misunderstands the task scope.
  • Add `--model claude-haiku-4-5` to headless CI runs for tasks that don't need Sonnet-level reasoning — 10-20x cost reduction for lint fixes, formatting, trivial summaries.
  • Log Claude's stderr separately in CI: `claude --print '...' 1>response.txt 2>tools.log` — this makes debugging failed runs much easier.
  • For tasks that involve reading many files, include the relevant file paths explicitly in your prompt. Claude will still find them via tools, but explicit paths reduce unnecessary file discovery calls.
  • Set a `--max-turns` limit on headless runs to prevent runaway sessions that exhaust your API budget on stuck tasks.

FAQ

Can Claude Code run completely without any terminal UI in headless mode?+

Yes. The --print flag disables the interactive TUI entirely. Claude runs as a standard CLI process: reads stdin/args, runs tools, writes response to stdout, exits. This is safe to run in environments with no TTY (Docker containers, GitHub Actions, cron jobs).

How do I pass a multi-line prompt in headless mode?+

Use a heredoc or pipe: `claude --print "$(cat prompt.txt)"` or `cat prompt.txt | claude --print`. For prompts with special shell characters, the pipe form is safer as it avoids shell escaping issues.

Does headless mode still read CLAUDE.md and settings.json?+

Yes — Claude Code reads CLAUDE.md and settings.json in headless mode exactly the same as interactive mode. Your project context and permissions configuration apply to all headless runs, which is why consistent project-level settings.json is valuable for teams.

What's the difference between --print and the Agent API?+

--print is a quick CLI wrapper. The Agent API (Anthropic SDK) gives you programmatic control — structured output, streaming, tool call intercepts, custom session state. Use --print for simple shell-script automation; use the SDK when you're building a product that calls Claude as part of its backend.

Can I run multiple Claude Code headless instances in parallel?+

Yes — each `claude --print` invocation is an independent process with no shared state. You can parallelize with `&` and `wait` in bash or GNU parallel. Watch your API rate limits — parallel runs consume quota simultaneously. Use git worktrees to give each parallel Claude a clean working directory.

Related Guides