◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Claude Code
⟐ Claude Code/2026-04-25Intermediate

Automating CI/CD Pipelines with Claude Code's --print and --output-format json

A practical guide to using Claude Code's --print flag and --output-format json in CI/CD pipelines. Covers automated code review in GitHub Actions, PR comment generation, and test failure analysis — with working code throughout.

claude-code132cicdgithub-actions4automation109json3headless14

Using Claude Code interactively in the terminal is great — but at some point you want to go further: run it automatically when a PR is opened, have it analyze test failures without manual input, or wire it into a deployment check.

For all of these, you need Claude Code in headless (non-interactive) mode. The two flags that make this possible are --print and --output-format json. This guide shows how to use them in practice, with complete working code for GitHub Actions integration.

What --print Does

--print runs Claude Code in headless mode. Instead of launching the interactive UI, it takes a prompt, returns a response to stdout, and exits.

# Normal startup — opens interactive UI
claude
 
# With --print — runs headless, exits after responding
claude --print "Please review this code"
 
# Pipe file contents directly
cat src/auth.ts | claude --print "Identify any security issues"

Without anything else, --print outputs plain text. That's sufficient for simple use cases, but when you need the output to drive downstream CI steps, structured JSON is much easier to work with.

Getting Structured Output with --output-format json

Combine --output-format json with --print to receive the response as JSON:

claude --print --output-format json "Please review this PR"

Example output (formatted):

{
  "type": "result",
  "subtype": "success",
  "result": "Review content here...",
  "session_id": "sess_abc123",
  "cost_usd": 0.0045,
  "duration_ms": 2341,
  "num_turns": 1
}

The result field contains the response text. cost_usd gives you an approximate token cost — useful for tracking CI spending over time.

Automated Code Review in GitHub Actions

Here's a complete workflow that runs when a PR is opened, has Claude review the diff, and posts the result as a PR comment.

Prerequisites

# Repository secrets to configure:
# ANTHROPIC_API_KEY — your Anthropic API key
# GITHUB_TOKEN — provided automatically by Actions

Workflow File

# .github/workflows/claude-review.yml
name: Claude Code Review
 
on:
  pull_request:
    types: [opened, synchronize]
    paths:
      - "src/**"
      - "lib/**"
 
jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      contents: read
 
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - 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 -- src/ lib/ > /tmp/pr_diff.txt
          echo "diff_size=$(wc -c < /tmp/pr_diff.txt)" >> $GITHUB_OUTPUT
 
      - name: Run Claude Review
        id: claude
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          # Trim large diffs to avoid context limit issues
          head -c 51200 /tmp/pr_diff.txt > /tmp/pr_diff_trimmed.txt
 
          REVIEW=$(claude --print --output-format json \
            --system "You are a senior engineer conducting a code review. Check for:
            1. Security issues (SQL injection, XSS, auth bypass, etc.)
            2. Performance issues (N+1 queries, unnecessary loops, memory leaks)
            3. Potential bugs (missing error handling, edge cases)
            4. Always acknowledge good implementation choices
            If there are no issues, say so explicitly." \
            "Please review the following PR diff:
 
          $(cat /tmp/pr_diff_trimmed.txt)")
 
          # Extract the result field from JSON
          echo "$REVIEW" | python3 -c "
          import json, sys
          data = json.load(sys.stdin)
          if data.get('subtype') == 'success':
              print(data['result'])
          else:
              print('Failed to generate review')
          " > /tmp/review_comment.txt
 
          echo "review_generated=true" >> $GITHUB_OUTPUT
 
      - name: Post Review Comment
        if: steps.claude.outputs.review_generated == 'true'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const reviewText = fs.readFileSync('/tmp/review_comment.txt', 'utf8');
 
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `## 🤖 Claude Code Review\n\n${reviewText}\n\n---\n*This review was generated automatically by Claude Code*`
            });

The workflow passes the diff to Claude via --system (for role context) and the prompt body (for the diff itself), extracts the JSON response, and posts it as a PR comment.

Analyzing Test Failures Automatically

A second practical use case: when tests fail in CI, have Claude generate root-cause hypotheses.

# .github/workflows/test-failure-analysis.yml
name: Test Failure Analysis
 
on:
  workflow_run:
    workflows: ["CI Tests"]
    types: [completed]
 
jobs:
  analyze:
    if: ${{ github.event.workflow_run.conclusion == 'failure' }}
    runs-on: ubuntu-latest
    permissions:
      actions: read
      issues: write
 
    steps:
      - uses: actions/checkout@v4
 
      - name: Download test logs
        uses: actions/download-artifact@v4
        with:
          name: test-results
          path: /tmp/test-results/
        continue-on-error: true
 
      - name: Analyze with Claude
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          LOG_FILE="/tmp/test-results/test-output.txt"
          if [ ! -f "$LOG_FILE" ]; then
            echo "No test log found" > "$LOG_FILE"
          fi
 
          ANALYSIS=$(claude --print --output-format json \
            "Here are test failure logs. List three probable root causes
            and how to verify each one.
 
          Log:
          $(head -c 10000 $LOG_FILE)")
 
          echo "$ANALYSIS" | python3 -c "
          import json, sys
          data = json.load(sys.stdin)
          print(data.get('result', 'Analysis failed'))
          " > /tmp/analysis.txt
 
          cat /tmp/analysis.txt

Local Use: Shell Script Integration

You don't have to go straight to CI. A local shell script is a good starting point:

#!/bin/bash
# scripts/ai-review.sh
# Usage: ./scripts/ai-review.sh [base-branch]
 
BASE="${1:-main}"
DIFF=$(git diff "$BASE"...HEAD 2>/dev/null)
 
if [ -z "$DIFF" ]; then
  echo "No diff found."
  exit 0
fi
 
echo "Running Claude Code review..."
 
RESULT=$(echo "$DIFF" | claude --print --output-format json \
  "Review this git diff. Focus on security and performance.")
 
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
  echo "Error: Claude Code exited with code $EXIT_CODE"
  exit 1
fi
 
echo "$RESULT" | python3 -c "
import json, sys
try:
    data = json.load(sys.stdin)
    print(data.get('result', 'Failed to get result'))
    cost = data.get('cost_usd', 0)
    print(f'\n--- Cost: \${cost:.4f} USD ---')
except json.JSONDecodeError:
    print('Failed to parse JSON')
    sys.exit(1)
"

Running this before committing catches issues that would otherwise only surface in code review.

Tracking Usage and Cost

Since --output-format json includes cost_usd, you can log CI spending systematically:

# scripts/track_claude_cost.py
import json
import sys
from datetime import datetime
 
def track_cost(json_output: str, run_name: str) -> None:
    """Log cost from Claude Code JSON output."""
    try:
        data = json.loads(json_output)
        cost = data.get("cost_usd", 0)
        tokens = data.get("usage", {})
 
        log_entry = {
            "timestamp": datetime.utcnow().isoformat(),
            "run": run_name,
            "cost_usd": cost,
            "usage": tokens,
        }
 
        with open("claude_usage.jsonl", "a") as f:
            f.write(json.dumps(log_entry) + "\n")
 
        print(f"[{run_name}] cost: ${cost:.4f} USD")
 
    except (json.JSONDecodeError, KeyError) as e:
        print(f"Cost tracking failed: {e}", file=sys.stderr)
 
if __name__ == "__main__":
    run_name = sys.argv[1] if len(sys.argv) > 1 else "unknown"
    json_input = sys.stdin.read()
    track_cost(json_input, run_name)

Common Mistakes

① Using --output-format json without --print

--output-format only works in combination with --print. It doesn't function standalone.

# ❌ This won't work
claude --output-format json "question"
 
# ✅ Correct
claude --print --output-format json "question"

② Sending oversized diffs

If input exceeds Claude's context window, you'll get errors or truncated responses. Trim to 50KB or split files before sending.

③ Rate limits under parallel CI runs

If ANTHROPIC_API_KEY has org-level usage limits, parallel CI jobs can hit rate limits. Issue a dedicated key for CI and set an appropriate usage cap.

Start with This Command

If you want to try it right now, paste this in your terminal:

# Have Claude review your current uncommitted changes
git diff HEAD | claude --print --output-format json \
  "What stands out about this change?" | \
  python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('result',''))"

Once you have a feel for --print and --output-format json, wiring it into GitHub Actions is a straightforward next step. Start with the diff review workflow, get comfortable with the JSON parsing, and expand from there.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⟐ Claude Code2026-08-09
One Transient 401 Replaced My Long-Lived Token — Giving Credentials a Provenance Field
A shared credential file can lose its long-lived token to a single transient 401. I injected exactly one 401 into a 24-worker fleet, measured the blast radius, and compared a provenance guard against full isolation.
⟐ Claude Code2026-04-30
Composing Claude Code Skills — Building Automation a Single Skill Can't Reach
A practical guide to going beyond one-skill-at-a-time and composing multiple Claude Code Skills into real workflows. Pipeline, branching, and meta-skill patterns with full implementation examples and the failure modes you should design against.
⟐ Claude Code2026-03-28
Running Claude Code × Playwright E2E Tests in Production — Pitfalls and Decision Criteria
Implementation patterns for running E2E tests with Claude Code and Playwright in production, plus how to measure whether the investment pays back. Includes the Cloudflare Workers and Pages preview deployment pitfalls, and a decision framework that indie developers can use to judge whether E2E tests are worth the investment.