CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
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-code129cicd2github-actions4automation95json3headless12

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-05-25
Notes from Seven Months of Running a Four-Site Auto-Publishing Pipeline with Claude Code and GitHub Actions
Lessons from running Claude Code scheduled tasks to auto-publish 16 articles a day across four Lab sites, including the Helpful Content System index collapse that forced a redesign of the quality gate, with the full Python gate code, token management, and rollback procedure.
Claude Code2026-05-09
Designing Claude Code Skills That Actually Run Unattended — Three Patterns to Avoid Permission-Dialog Stalls
I learned the hard way that Claude Code skills can silently freeze in scheduled runs because of permission dialogs. Here are three implementation patterns that keep file work, path detection, and recovery fully autonomous — distilled from a month of running content automation across four sites.
Claude Code2026-03-23
Claude Code --bare Flag: Headless Automation and Scripted Execution
Learn how to use Claude Code's new --bare flag for fast, lightweight scripted automation. This guide covers CI/CD pipelines, batch processing, and safety best practices for headless Claude Code execution.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →