headroom.walls.sh · ci

Claude Code in CI

Run Claude Code in GitHub Actions and other CI pipelines with --print for non-interactive output — automated PR reviews, code analysis, and documentation generation.

The key flag: --print

By default, Claude Code runs in interactive mode — it opens a session where you type prompts and get responses. In CI, you need it to run once, print output, and exit. That's --print:

claude --print "review this diff for security issues"

With --print, Claude Code reads stdin (if piped) or uses only the flags you provide, outputs its response to stdout, then exits with code 0 on success or non-zero on error. No terminal UI, no session state, fully scriptable.

GitHub Actions: basic setup

name: Claude Code Review

on:
  pull_request:
    types: [opened, synchronize]

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

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

      - name: Review PR diff
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
        run: |
          git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr.diff
          claude --print "Review this pull request diff for bugs, security issues, and style problems. Be concise." < /tmp/pr.diff

Setting the secret

In your GitHub repo: Settings → Secrets and variables → Actions → New repository secret. Name: ANTHROPIC_API_KEY, value: your key from console.anthropic.com.

Common CI use cases

PR review with GitHub comment

      - name: Review and comment
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr.diff
          REVIEW=$(claude --print "Review this diff. Flag bugs and security issues only — skip style nitpicks. Use markdown." < /tmp/pr.diff)
          gh pr comment ${{ github.event.pull_request.number }} --body "$REVIEW"

Check for TODO comments before merge

      - name: Flag TODOs
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
        run: |
          TODOS=$(grep -rn "TODO\|FIXME\|HACK\|XXX" src/ || true)
          if [ -n "$TODOS" ]; then
            echo "$TODOS" | claude --print "Categorize these TODO comments by severity: blocker (must fix before merge), warning (should fix soon), info (nice to have)."
          fi

Generate release notes from commits

      - name: Generate release notes
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
        run: |
          git log --oneline v${{ github.event.release.tag_name }}^..HEAD > /tmp/commits.txt
          claude --print "Write user-facing release notes from these git commits. Group by: new features, bug fixes, breaking changes. Skip chores and ci commits." < /tmp/commits.txt > RELEASE_NOTES.md

Important flags for CI

Flag / env varWhat it does in CI
--printNon-interactive: single prompt → stdout → exit. Essential for CI.
--output-format jsonStructured JSON output — easier to parse in scripts than raw text.
--max-turns NLimit how many tool-use turns Claude Code can take (cost control).
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1Disables telemetry and update checks — faster CI, no noise.
--allowedTools "Read,Bash"Restrict which tools Claude Code can use in this run.

Piping files and stdin

Claude Code with --print reads stdin when you pipe to it. This is the cleanest way to pass large inputs like diffs, log files, or generated content:

# Pipe a file
cat error.log | claude --print "summarize the errors in this log"

# Pipe git diff
git diff HEAD~1 | claude --print "what changed and why does it matter"

# Pipe and capture output
SUMMARY=$(cat report.json | claude --print "extract the key metrics as a bullet list")

Exit codes and error handling

claude --print "check this code" < src/main.py
EXIT=$?
if [ $EXIT -ne 0 ]; then
  echo "Claude Code failed with exit code $EXIT"
  exit 1
fi

Exit 0 = success. Non-zero = API error, auth failure, or --max-turns exceeded. Always check the exit code in scripts that gate a deploy or merge.

Cost control in CI

Each CI run makes real API calls — they count against your 5-hour session and 7-day weekly limits, and cost money. A few practices that help:

Use --max-turns 5 to cap agentic tool use. Scope your prompts tightly (diff only, not full repo). Cache the Claude Code install step. Run only on PRs that touch relevant paths using paths: in your workflow trigger.

Headroom shows your Claude Code session (5h) and weekly (7d) utilization in the macOS menu bar — useful when CI pipelines are eating into your local coding quota. The numbers update live from the same data Claude Code tracks itself.

Download Headroom v0.3.5 — free
brew install patwalls/tap/headroom

Environment variables
Agent mode and subagents
Permissions and allow rules
Rate limits and windows