Skip to content

Chapter 12: CI/CD & GitHub Actions ​

Learning Objectives ​

  • Set up claude-code-action for automated PR review with copy-paste YAML configs
  • Configure automatic issue triage with labels, priority, and assignment
  • Build a CI security gate that blocks merges when Claude finds vulnerabilities
  • Integrate Claude into existing CI pipelines alongside your test suite
  • Handle API keys, rate limits, and cost controls in CI environments

Appendix links: A05 Tool Calling Internals explains how Claude selects tools and processes JSON Schema definitions, which is the same mechanism powering claude -p in CI scripts. See also A12 Safety & Alignment for how permission bypass in CI requires compensating security controls.

How Claude Code Works in CI ​

There are two integration points:

  1. claude-code-action -- A GitHub Action that triggers Claude on PRs and issues. It runs Claude in the cloud, not on your runner
  2. claude -p in scripts -- Run Claude as a CLI tool in any CI pipeline (GitHub Actions, GitLab CI, Jenkins, CircleCI). Runs on the CI runner itself
GitHub Event (PR opened, issue created, push)
        |
        v
+-------------------------------+
|  claude-code-action           |  Cloud-based, managed by Anthropic
|  - Reviews PR diffs           |  Needs: ANTHROPIC_API_KEY
|  - Posts review comments      |  Cost: per-token
|  - Creates PRs from issues    |
+-------------------------------+
        or
+-------------------------------+
|  claude -p in CI script       |  Runs on your CI runner
|  - Custom analysis            |  Needs: ANTHROPIC_API_KEY + runner setup
|  - Quality gates              |  Cost: per-token + runner minutes
|  - Security scans             |
+-------------------------------+

Demo 31: Complete GitHub Actions PR Review ​

31
GitHub Actions PR Review
Intermediate~15 min

The YAML Config ​

Create .github/workflows/claude-review.yml:

yaml
name: Claude Code Review
on:
  pull_request:
    types: [opened, synchronize, reopened]
  issue_comment:
    types: [created]

# Required: write permissions for PR comments
permissions:
  contents: read
  pull-requests: write
  issues: write

jobs:
  review:
    # Only run on PR events, or on issue comments that mention @claude
    if: |
      github.event_name == 'pull_request' ||
      (github.event_name == 'issue_comment' &&
       github.event.issue.pull_request &&
       contains(github.event.comment.body, '@claude'))
    runs-on: ubuntu-latest
    steps:
      - name: Run Claude Code Review
        uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

          # Optional: customize what Claude focuses on
          review_instructions: |
            Focus your review on:
            1. Security vulnerabilities (SQL injection, XSS, auth bypass)
            2. Error handling gaps (unhandled promises, missing try/catch)
            3. Performance issues (N+1 queries, missing indexes, memory leaks)
            4. Type safety (any casts, missing null checks)

            Do NOT comment on:
            - Code style or formatting (handled by linters)
            - Import ordering
            - Minor naming preferences

            For each issue found, rate severity: critical / warning / suggestion

          # Optional: limit the model to control costs
          model: claude-sonnet-4-6

          # Optional: set max tokens to prevent runaway costs
          max_tokens: 16384

What This Does ​

When a PR is opened or updated:

  1. Claude reads the PR diff and all changed files
  2. Claude analyzes the changes against the review instructions
  3. Claude posts inline review comments on specific lines
  4. Claude posts a summary comment with the overall assessment

When someone comments "@claude can you also check the error handling in the payment module?":

  1. Claude reads the comment and the PR context
  2. Claude performs the requested additional analysis
  3. Claude responds as a reply to the comment

What the PR review looks like on GitHub:

terminal
  GitHub PR #203: "Add user search endpoint"

  claude-code-action (bot) reviewed 3 minutes ago

  src/api/users.ts line 42:
  +---------------------------------------------------------------+
  | WARNING: SQL injection risk                                    |
  |                                                                |
  | The search query is interpolated directly into the SQL string: |
  |   const results = await db.query(                              |
  |     `SELECT * FROM users WHERE name LIKE '%${query}%'`        |
  |   );                                                           |
  |                                                                |
  | Use parameterized queries instead:                             |
  |   const results = await db.query(                              |
  |     'SELECT * FROM users WHERE name LIKE $1',                  |
  |     [`%${query}%`]                                             |
  |   );                                                           |
  +---------------------------------------------------------------+

  src/api/users.ts line 58:
  +---------------------------------------------------------------+
  | SUGGESTION: Missing error handling                             |
  |                                                                |
  | The database query has no try/catch. If the query fails,       |
  | the error will propagate as an unhandled promise rejection.    |
  | Wrap in try/catch and return a proper 500 response.            |
  +---------------------------------------------------------------+

  Summary:
  +---------------------------------------------------------------+
  | Found 1 warning and 1 suggestion in 3 files scanned.          |
  |                                                                |
  | The SQL injection on line 42 should be fixed before merging.   |
  | The missing error handling is a good practice improvement.     |
  +---------------------------------------------------------------+

What Just Happened? ​

1
GitHub API
PR diff and changed files
↓
2
Read
review_instructions from YAML
↓
3
Analysis
each changed file
↓
4
GitHub API
post inline review comments

Storing Your API Key ​

bash
# In your repository settings:
# Settings > Secrets and variables > Actions > New repository secret
# Name: ANTHROPIC_API_KEY
# Value: sk-ant-...

Demo 32: Auto-Triage Incoming Issues ​

32
Auto-Triage Incoming Issues
Intermediate~15 min

The YAML Config ​

Create .github/workflows/claude-triage.yml:

yaml
name: Claude Issue Triage
on:
  issues:
    types: [opened]

permissions:
  contents: read
  issues: write

jobs:
  triage:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Triage with Claude
        uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          model: claude-sonnet-4-6

          # Claude reads the issue and the codebase, then triages
          direct_prompt: |
            Analyze this GitHub issue and triage it.

            Issue title: ${{ github.event.issue.title }}
            Issue body: ${{ github.event.issue.body }}

            Steps:
            1. Read the issue carefully
            2. Search the codebase to understand if this is a real bug,
               a feature request, or a question
            3. Determine:
               - Type: bug | feature | question | documentation
               - Priority: P0-critical | P1-high | P2-medium | P3-low
               - Component: which part of the codebase is affected
               - Assignee suggestion: based on git blame of affected files

            4. Add appropriate labels using the GitHub MCP tools
            5. If it is a bug, check if there is an obvious fix and mention it
            6. Post a comment summarizing the triage decision

            Label mapping:
            - Type labels: bug, enhancement, question, documentation
            - Priority labels: P0-critical, P1-high, P2-medium, P3-low
            - Component labels: api, frontend, database, auth, infra

          allowed_tools: |
            Read
            Glob
            Grep
            Bash(git log *)
            Bash(git blame *)
            mcp__github__*

What This Looks Like in Practice ​

Someone opens an issue:

Title: Login fails when email contains a plus sign

Body: When I try to log in with john+test@gmail.com, I get a 400 error. This worked last week. Using Chrome 124 on macOS.

Within 2 minutes, Claude:

  1. Searches the codebase for email validation logic
  2. Finds the regex in src/auth/validators.ts that does not handle +
  3. Checks git log to see this regex was changed 3 days ago in PR #189
  4. Adds labels: bug, P1-high, auth
  5. Posts a comment:
terminal
  GitHub Issue #210: "Login fails when email contains a plus sign"

  Labels added: bug, P1-high, auth

  claude-code-action (bot) commented 2 minutes ago:
  +---------------------------------------------------------------+
  | Triage Summary                                                |
  |                                                                |
  | Type: Bug | Priority: P1-high | Component: auth                |
  |                                                                |
  | The email validation regex in src/auth/validators.ts:42 was    |
  | updated in PR #189 (3 days ago) and no longer allows + in the  |
  | local part of email addresses.                                 |
  |                                                                |
  | Current regex: ^[a-zA-Z0-9.]+@                                 |
  | Should be:     ^[a-zA-Z0-9.+]+@                                |
  |                                                                |
  | Suggested assignee: @alice (last modified this file)            |
  |                                                                |
  | This is likely a quick fix. The regex change in PR #189 was    |
  | intended to block special characters for security, but + is a  |
  | valid RFC 5321 character in email local parts.                 |
  +---------------------------------------------------------------+

What Just Happened? ​

1
Grep
email validation patterns in src/
↓
2
Read
src/auth/validators.ts
↓
3
Bash
git log src/auth/validators.ts
↓
4
Bash
git blame src/auth/validators.ts
↓
5
MCP (GitHub)
add labels and post comment

Demo 33: CI Security Gate ​

33
CI Security Gate
Advanced~20 min

The Goal ​

Add a CI check that runs on every PR and blocks the merge if Claude identifies security vulnerabilities in the changed code. This runs alongside your existing test suite.

The YAML Config ​

Create .github/workflows/claude-security.yml:

yaml
name: Claude Security Scan
on:
  pull_request:
    types: [opened, synchronize, reopened]

permissions:
  contents: read
  pull-requests: write

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Need full history for diff

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

      - name: Get changed files
        id: changed
        run: |
          FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD \
            | grep -E '\.(ts|tsx|js|jsx|py|go|rs|java)$' \
            | head -50)
          echo "files=$FILES" >> $GITHUB_OUTPUT

      - name: Run Security Scan
        id: scan
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          cat > /tmp/security-prompt.txt << 'PROMPT'
          You are a security auditor. Analyze the following code changes for
          security vulnerabilities.

          Check for:
          1. SQL injection (string concatenation in queries)
          2. XSS (unescaped user input in HTML/JSX)
          3. Authentication bypass (missing auth checks on endpoints)
          4. Path traversal (unsanitized file paths from user input)
          5. Secrets in code (hardcoded API keys, passwords, tokens)
          6. Insecure deserialization
          7. SSRF (user-controlled URLs in server-side requests)
          8. Command injection (user input in shell commands)

          For each vulnerability found, output a JSON object:
          {
            "file": "path/to/file.ts",
            "line": 42,
            "severity": "critical|high|medium|low",
            "type": "sql-injection",
            "description": "User input concatenated into SQL query without parameterization",
            "suggestion": "Use parameterized queries: db.query('SELECT * FROM users WHERE id = $1', [userId])"
          }

          If no vulnerabilities are found, output:
          {"status": "clean", "files_scanned": 5}

          Output ONLY valid JSON (one object per line, no other text).
          PROMPT

          # Get the diff and pipe it to Claude
          git diff origin/${{ github.base_ref }}...HEAD -- ${{ steps.changed.outputs.files }} \
            | claude -p "$(cat /tmp/security-prompt.txt)" \
              --permission-mode bypassPermissions \
              --output-format json \
              --max-tokens 8192 \
            > /tmp/security-results.json

          # Check for critical or high severity findings
          if grep -q '"severity": "critical"' /tmp/security-results.json; then
            echo "has_critical=true" >> $GITHUB_OUTPUT
          else
            echo "has_critical=false" >> $GITHUB_OUTPUT
          fi

          if grep -q '"severity": "high"' /tmp/security-results.json; then
            echo "has_high=true" >> $GITHUB_OUTPUT
          else
            echo "has_high=false" >> $GITHUB_OUTPUT
          fi

      - name: Post Results as PR Comment
        if: always()
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const results = fs.readFileSync('/tmp/security-results.json', 'utf8');

            let body = '## Security Scan Results\n\n';

            const lines = results.trim().split('\n').filter(l => l.trim());
            const findings = lines.map(l => {
              try { return JSON.parse(l); } catch { return null; }
            }).filter(Boolean);

            if (findings.length === 1 && findings[0].status === 'clean') {
              body += 'No security vulnerabilities found. Files scanned: ' +
                findings[0].files_scanned + '\n';
            } else {
              body += '| Severity | File | Line | Type | Description |\n';
              body += '|----------|------|------|------|-------------|\n';
              for (const f of findings) {
                if (f.severity) {
                  const icon = f.severity === 'critical' ? '🔴' :
                    f.severity === 'high' ? '🟠' :
                    f.severity === 'medium' ? '🟡' : '🟢';
                  body += `| ${icon} ${f.severity} | ${f.file} | ${f.line} | ` +
                    `${f.type} | ${f.description} |\n`;
                }
              }
              body += '\n---\n';
              for (const f of findings) {
                if (f.suggestion) {
                  body += `\n**${f.file}:${f.line}** - ${f.suggestion}\n`;
                }
              }
            }

            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: body
            });

      - name: Fail on Critical Findings
        if: steps.scan.outputs.has_critical == 'true'
        run: |
          echo "CRITICAL security vulnerabilities found. Blocking merge."
          echo "Review the PR comment for details."
          exit 1

      - name: Warn on High Findings
        if: steps.scan.outputs.has_high == 'true'
        run: |
          echo "::warning::HIGH severity security findings detected. Review recommended before merge."

What the CI Output Looks Like ​

When vulnerabilities are found (merge blocked):

terminal
  GitHub Actions: Claude Security Scan

  Run Security Scan .............. 45s
    Scanning 8 changed files...
    Analyzing diff against security checklist...

  Post Results as PR Comment ..... 2s
    Posted security scan results as PR comment

  Fail on Critical Findings ...... FAILED
    CRITICAL security vulnerabilities found. Blocking merge.
    Review the PR comment for details.

  ========================================
  Job failed. 1 critical finding must be resolved before merge.

The PR comment posted by the security gate:

terminal
  GitHub PR #215: "Add admin API endpoints"

  github-actions (bot) commented 1 minute ago:

  ## Security Scan Results

  | Severity | File           | Line | Type           | Description                    |
  |----------|----------------|------|----------------|--------------------------------|
  | CRITICAL | src/admin.ts   | 23   | auth-bypass    | Admin endpoint has no auth     |
  |          |                |      |                | middleware check                |
  | HIGH     | src/admin.ts   | 45   | sql-injection  | User input in query string     |
  | MEDIUM   | src/admin.ts   | 67   | missing-rate   | No rate limiting on admin      |
  |          |                |      |                | endpoints                      |

  ---

  src/admin.ts:23 - Add authentication middleware:
    router.use('/admin', authMiddleware, adminRouter)

  src/admin.ts:45 - Use parameterized queries:
    db.query('SELECT * FROM users WHERE role = $1', [role])

  src/admin.ts:67 - Add rate limiting:
    router.use('/admin', rateLimit({ windowMs: 15*60*1000, max: 100 }))

When the scan is clean:

terminal
  GitHub Actions: Claude Security Scan

  Run Security Scan .............. 32s
    Scanning 3 changed files...
    Analyzing diff against security checklist...

  Post Results as PR Comment ..... 2s

  ## Security Scan Results
  No security vulnerabilities found. Files scanned: 3

What Just Happened? ​

1
Bash
git diff to get changed files
↓
2
Bash
claude -p with security prompt
↓
3
GitHub API
post PR comment
↓
4
CI gate
exit 1 on critical

Making It a Required Check ​

In your GitHub repository settings:

  1. Go to Settings, then Branches, then Branch protection rules
  2. Edit (or create) the rule for main
  3. Enable "Require status checks to pass before merging"
  4. Search for "Claude Security Scan" and add it as a required check

Now PRs with critical security vulnerabilities cannot be merged until the issues are resolved.

Cost Control ​

The security scan uses claude -p (pipe mode) with the diff as input. This is efficient:

  • Only the changed code is analyzed, not the entire codebase
  • --max-tokens 8192 caps the response cost
  • Sonnet is used by default (cheaper than Opus)
  • Average cost per PR: $0.02-0.15 depending on diff size

For large repositories, add a file count limit:

yaml
# Only scan the first 50 changed files
FILES=$(git diff --name-only origin/main...HEAD | head -50)

Combining All Three: A Complete CI Pipeline ​

Here is how the three workflows fit together:

yaml
# .github/workflows/claude-ci.yml
name: Claude CI Pipeline
on:
  pull_request:
    types: [opened, synchronize, reopened]
  issues:
    types: [opened]
  issue_comment:
    types: [created]

permissions:
  contents: read
  pull-requests: write
  issues: write

jobs:
  # Job 1: Review PRs
  pr-review:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          model: claude-sonnet-4-6
          review_instructions: |
            Focus on correctness, security, and error handling.
            Skip style comments.

  # Job 2: Security gate (blocks merge on critical findings)
  security-gate:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - run: npm install -g @anthropic-ai/claude-code
      - run: |
          git diff origin/${{ github.base_ref }}...HEAD \
            | claude -p "Scan for security vulnerabilities. Output JSON." \
              --permission-mode bypassPermissions \
            | tee /tmp/results.json
          grep -q '"severity": "critical"' /tmp/results.json && exit 1 || true
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

  # Job 3: Triage new issues
  issue-triage:
    if: github.event_name == 'issues'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          model: claude-sonnet-4-6
          direct_prompt: |
            Triage this issue. Add type and priority labels.
            Issue: ${{ github.event.issue.title }}
            Body: ${{ github.event.issue.body }}

  # Job 4: Respond to @claude mentions in PR comments
  claude-assist:
    if: |
      github.event_name == 'issue_comment' &&
      contains(github.event.comment.body, '@claude')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

What the combined pipeline looks like in the GitHub Actions tab:

terminal
  GitHub Actions: Claude CI Pipeline

  Triggered by: pull_request (opened)

  pr-review ................. PASSED  (1m 23s)
    Claude reviewed 5 files, posted 2 inline comments

  security-gate ............. PASSED  (48s)
    No critical vulnerabilities found. 5 files scanned.

  issue-triage .............. SKIPPED
    (Only runs on issue events)

  claude-assist ............. SKIPPED
    (Only runs on @claude mentions)

When Things Go Wrong ​

CI Token Permissions Insufficient ​

Symptom: The workflow fails with a 403 error when trying to post PR comments or add labels.

terminal
  GitHub Actions: Claude Code Review

  Run Claude Code Review ........ FAILED

  Error: Resource not accessible by integration
  HttpError: 403 - Resource not accessible by integration

  This usually means the GITHUB_TOKEN does not have sufficient permissions.

Root cause: The permissions block in your workflow YAML is missing or incomplete. GitHub Actions uses a restricted token by default.

Fix: Add the required permissions to your workflow:

yaml
permissions:
  contents: read        # Read repo files and diffs
  pull-requests: write  # Post PR review comments
  issues: write         # Add labels, post issue comments

If you are using a fine-grained personal access token instead of the default GITHUB_TOKEN, ensure it has:

  • Repository access: Contents (read), Pull requests (write), Issues (write)
  • For organization repos: the token must be approved by an org admin

API Rate Limiting ​

Symptom: The security scan or review fails intermittently with rate limit errors.

terminal
  Run Security Scan .............. FAILED

  Error: 429 Too Many Requests
  Rate limit exceeded. Please retry after 60 seconds.

  Your organization has sent too many requests to the Anthropic API.
  Current limit: 1000 requests per minute.

Root cause: Multiple PRs opened simultaneously, or the workflow is re-triggered on every push to a PR branch, exhausting your API rate limit.

Fix -- reduce trigger frequency:

yaml
on:
  pull_request:
    types: [opened, reopened]  # Remove 'synchronize' to avoid re-running on every push
    paths:
      - 'src/**'               # Only trigger on source code changes
      - '!src/**/*.test.ts'    # Skip test-only changes

Fix -- add concurrency control:

yaml
concurrency:
  group: claude-review-${{ github.event.pull_request.number }}
  cancel-in-progress: true  # Cancel previous runs when new commits are pushed

Fix -- add retry logic for transient failures:

yaml
      - name: Run Security Scan (with retry)
        uses: nick-fields/retry@v3
        with:
          timeout_minutes: 5
          max_attempts: 3
          retry_wait_seconds: 60
          command: |
            git diff origin/${{ github.base_ref }}...HEAD \
              | claude -p "Scan for security vulnerabilities." \
                --permission-mode bypassPermissions \
              > /tmp/security-results.json

Cost Control Failures ​

Symptom: Your monthly Anthropic API bill is unexpectedly high due to CI usage.

terminal
  Anthropic Dashboard:
  ─────────────────────────────────
  Usage this month: $847.32
  Budget limit:     $200.00

  Top consumers:
    claude-code-action (PR review):  $312.00  (2,400 runs)
    claude -p (security scan):       $289.00  (2,400 runs)
    claude -p (issue triage):        $246.32  (1,800 runs)

Root causes and fixes:

  1. No max_tokens cap: Without --max-tokens, Claude may generate very long responses for large diffs.
yaml
# Always set max_tokens in CI
max_tokens: 8192   # For claude-code-action
--max-tokens 8192  # For claude -p
  1. Using Opus instead of Sonnet: Opus costs roughly 15x more per token than Sonnet. For automated reviews, Sonnet is usually sufficient.
yaml
model: claude-sonnet-4-6  # Not claude-opus-4-7
  1. Running on every push to a PR: If a developer pushes 10 commits to a PR branch, the review runs 10 times.
yaml
# Use concurrency to cancel previous runs
concurrency:
  group: claude-${{ github.event.pull_request.number }}
  cancel-in-progress: true
  1. No file filtering: Scanning documentation or config changes wastes tokens.
yaml
# Only scan source code files
FILES=$(git diff --name-only origin/main...HEAD \
  | grep -E '\.(ts|tsx|js|jsx|py|go|rs|java)$' \
  | head -50)
  1. Set a budget alert: Configure Anthropic's API dashboard to alert you when spending exceeds a threshold.

Cost estimation formula:

Cost per PR review = (input_tokens + output_tokens) * price_per_token
Average PR diff:    ~2,000 tokens input
Average review:     ~1,000 tokens output
Sonnet pricing:     ~$0.003 per 1K input, ~$0.015 per 1K output
Cost per review:    ~$0.006 + ~$0.015 = ~$0.02

100 PRs/week = ~$2/week = ~$8/month (review only)
Add security scan: ~$16/month total
Add issue triage:  ~$24/month total

ANTHROPIC_API_KEY Secret Not Found ​

Symptom: The workflow fails immediately because the API key is not available.

terminal
  Run Claude Code Review ........ FAILED

  Error: ANTHROPIC_API_KEY is not set.
  Please add your API key as a repository secret.

Fix:

bash
# 1. Go to your repository on GitHub
# 2. Settings > Secrets and variables > Actions
# 3. Click "New repository secret"
# 4. Name: ANTHROPIC_API_KEY
# 5. Value: your Anthropic API key (starts with sk-ant-)

For organization repositories: The secret must be set at the organization level or the repository must be granted access to the organization secret.

For forked repositories: GitHub Actions does not pass secrets to workflows triggered by pull requests from forks. This is a security feature. Use pull_request_target instead of pull_request if you need to review fork PRs, but be aware of the security implications.


Knowledge Check ​

What is the difference between claude-code-action and running claude -p in a CI script?
claude-code-action runs on your CI runner; claude -p runs in the cloud
claude-code-action runs in the cloud and handles GitHub integration; claude -p runs on your CI runner for custom analysis
They are the same thing with different names
claude-code-action is free; claude -p requires an API key
Your CI security scan blocks a PR merge. The developer says the finding is a false positive. What is the best approach?
Disable the security scan for this PR
Remove the branch protection rule temporarily
Have the developer comment @claude with context explaining why it is safe, then re-run the scan
Switch from Sonnet to Haiku for faster, less thorough scans
Your Anthropic API bill for CI is unexpectedly high. Which of these changes will have the BIGGEST cost impact?
Adding max_tokens: 8192 to limit response length
Adding concurrency control with cancel-in-progress to avoid duplicate runs
Switching from Sonnet to Haiku
Adding a paths filter to only scan source code changes

Chapter Summary ​

  • claude-code-action handles PR review and issue triage with minimal configuration -- drop the YAML in and it works
  • claude -p in CI scripts gives you full control for custom analysis and quality gates
  • Security gates can block merges by exiting with code 1 on critical findings
  • Cost control: use Sonnet, cap max tokens, add concurrency control, and only analyze changed files (not the full repo)
  • All three modes (review, triage, @-mention responses) can run from a single workflow file
  • CI token permissions must be explicitly declared in the workflow YAML
  • Rate limiting and cost spikes are the most common operational issues -- add concurrency control and budget alerts from day one

Further reading: A05 Tool Calling Internals explains the JSON Schema tool selection mechanism that powers claude -p in CI. A12 Safety & Alignment discusses why bypassPermissions in CI requires compensating controls like restricted runner environments and audit logging.

Next: Chapter 13: Agent SDK

Released under MIT License