Claude Code in CI/CD Pipelines
Non-interactive mode turns Claude Code into a CI step. Auto-review PR diffs, auto-fix lint errors, generate test stubs, enforce CLAUDE.md conventions — all triggered on every push. Here is the full playbook.
Claude Code has a non-interactive mode. Pass it a prompt, pipe it context, and it runs to completion, outputs a result, and exits with a code. No terminal required. No human in the loop. It is just a program.
This is the unlocking insight for CI/CD integration. Claude Code becomes a step in your GitHub Actions workflow — a step that can review code, enforce standards, generate tests, fix lint errors, and write changelogs. Every push through the same intelligence. Zero marginal effort.
The -p Flag: Non-Interactive Mode
claude -p "Review this code for security issues."
That is it. Claude Code reads the flag, executes the prompt against the current directory, outputs the result to stdout, and exits. Exit code 0 means success. Non-zero means failure. Your CI pipeline treats it like any other command.
Combine with --output-format:
# Plain text output (default)
claude -p "Summarize the changes in this PR." --output-format text
# JSON output for programmatic processing
claude -p "Check for any TODO comments and return them as a list." \
--output-format json | jq '.result'
# Streaming JSON for long outputs
claude -p "Generate full test coverage for this module." \
--output-format stream-json
The --output-format json mode returns a structured object with .result containing the response text, .cost with the token cost, and .session_id for traceability.
A Complete GitHub Actions Workflow
# .github/workflows/claude-review.yml
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
claude-review:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for diff
- 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: Get PR diff
id: diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr-diff.txt
echo "diff_size=$(wc -l < /tmp/pr-diff.txt)" >> $GITHUB_OUTPUT
- name: Claude Security Review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
DIFF=$(cat /tmp/pr-diff.txt)
claude -p "Review this diff for security issues only. Output: issue, severity, file, line, fix. If no issues, output 'CLEAN'.
Diff:
$DIFF" --output-format text > /tmp/review.txt
cat /tmp/review.txt
# Fail if critical issues found
if grep -qi "critical" /tmp/review.txt; then
echo "::error::Critical security issues found"
exit 1
fi
- name: Post Review Comment
if: always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs')
const review = fs.readFileSync('/tmp/review.txt', 'utf8')
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Claude Code Review\n\`\`\`\n${review}\n\`\`\``
})
Patterns That Compound
Auto-lint-fix and commit back:
- name: Claude Auto-Fix Lint
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
npm run lint 2>&1 > /tmp/lint-errors.txt || true
if [ -s /tmp/lint-errors.txt ]; then
ERRORS=$(cat /tmp/lint-errors.txt)
claude -p "Fix these lint errors: $ERRORS" --dangerously-skip-permissions
git config user.email "ci@github.com"
git config user.name "Claude Code CI"
git add -A && git commit -m "fix: auto-fix lint errors [skip ci]" || true
git push
fi
Generate test stubs for new code:
- name: Generate Missing Tests
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
NEW_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '\.(ts|js|py)$' | grep -v '\.test\.' | grep -v 'spec')
if [ -n "$NEW_FILES" ]; then
claude -p "For each of these new files, generate a test file if one does not exist: $NEW_FILES" \
--dangerously-skip-permissions
fi
Changelog generation on merge:
- name: Generate Changelog Entry
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
COMMITS=$(git log --oneline -20)
claude -p "Write a CHANGELOG.md entry for these commits: $COMMITS" \
--output-format text >> CHANGELOG.md
git add CHANGELOG.md && git commit -m "docs: update changelog [skip ci]" || true
Cost and Rate Limit Considerations
Claude Code in CI consumes API tokens. The cost per run is small but compounds across many PRs:
- Small diff (50 lines): ~$0.01
- Medium diff (500 lines): ~$0.05
- Large diff (2000+ lines): ~$0.10–0.25
For active repos with many PRs, set a monthly budget alert. Cap diff size passed to Claude:
# Limit context to prevent runaway costs on large PRs
DIFF=$(git diff origin/${{ github.base_ref }}...HEAD | head -500)
Rate limits on the standard API tier are generous for CI use — 50+ requests per minute on most plans. If you hit limits, add retry logic with exponential backoff.
The opportunity is a CI pipeline that enforces your standards on every commit without adding overhead to your review process. The security is the quality gates that prevent bad code from reaching production.
The CLAUDE.md Problem in CI
Your global ~/.claude/CLAUDE.md is on your local machine, not in the CI runner. Project-level CLAUDE.md is committed to the repo and available in CI — use it. If your CI step needs to enforce project conventions, those conventions must be in the project CLAUDE.md, not the global one.
Lesson 62 Drill
Add one Claude Code step to a real workflow. The simplest useful one: a PR comment that summarizes what changed and whether there are any obvious issues. Under 20 lines of YAML. Run it on your next PR and read what Claude says about your own work. Iterate the prompt based on what is useful.
Once that runs cleanly, add the failure condition — one specific type of issue that should fail the build. Now you have a real quality gate.
Bottom Line
claude -p "prompt" is the non-interactive entry point. Configure it in GitHub Actions with ANTHROPIC_API_KEY in repository secrets. Use --output-format json for programmatic output. Exit code 0 passes, non-zero fails the build. CI Claude cannot read your global CLAUDE.md — put conventions in the project file. Start with a PR review comment, add a failure condition, build from there. Every PR through the same quality lens, zero marginal cost to the reviewer.