Chapter 12: CI/CD 与 GitHub Actions
学习目标
- 配置
claude-code-action实现自动 PR Review(可直接复制粘贴的 YAML 配置) - 配置自动 Issue 分流:添加标签、优先级和负责人
- 构建 CI 安全门禁,Claude 发现漏洞时阻止合并
- 将 Claude 集成到现有 CI 流水线中,与测试套件并行运行
- 在 CI 环境中处理 API 密钥、速率限制和成本控制
附录链接:A05 工具调用内部机制 解释了 Claude 如何选择工具和处理 JSON Schema 定义,这也是 CI 脚本中
claude -p的底层机制。另见 A12 安全与对齐 了解 CI 中的权限绕过为何需要补偿性安全控制。
Claude Code 在 CI 中的工作方式
两个集成点:
- claude-code-action -- 一个在 PR 和 Issue 上触发 Claude 的 GitHub Action。Claude 在云端运行,不在你的 runner 上
claude -p脚本 -- 在任何 CI 流水线中(GitHub Actions、GitLab CI、Jenkins、CircleCI)将 Claude 作为 CLI 工具运行。在 CI runner 上执行
GitHub 事件(PR 打开、Issue 创建、push)
|
v
+-------------------------------+
| claude-code-action | 云端运行,Anthropic 管理
| - 审查 PR diff | 需要:ANTHROPIC_API_KEY
| - 发布审查评论 | 费用:按 token 计
| - 从 Issue 创建 PR |
+-------------------------------+
or
+-------------------------------+
| claude -p(CI 脚本中) | 在你的 CI runner 上运行
| - 自定义分析 | 需要:ANTHROPIC_API_KEY + runner 配置
| - 质量门禁 | 费用:按 token 计 + runner 分钟数
| - 安全扫描 |
+-------------------------------+Demo 31: 完整的 GitHub Actions PR Review
YAML 配置
创建 .github/workflows/claude-review.yml:
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
issue_comment:
types: [created]
# 必需:PR 评论的写权限
permissions:
contents: read
pull-requests: write
issues: write
jobs:
review:
# 仅在 PR 事件或提到 @claude 的 Issue 评论时运行
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 }}
# 可选:自定义 Claude 关注的重点
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
# 可选:限制模型以控制成本
model: claude-sonnet-4-6
# 可选:设置最大 token 数防止成本失控
max_tokens: 16384这个配置做了什么
当 PR 被打开或更新时:
- Claude 读取 PR diff 和所有修改的文件
- Claude 根据审查指令分析变更
- Claude 在具体行上发布 inline review 评论
- Claude 发布总结评论和整体评估
当有人评论 "@claude can you also check the error handling in the payment module?":
- Claude 读取评论和 PR 上下文
- Claude 执行请求的额外分析
- Claude 作为评论回复作出响应
PR Review 在 GitHub 上的效果:
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. |
+---------------------------------------------------------------+刚才发生了什么?
存储 API 密钥
# 在你的仓库设置中:
# Settings > Secrets and variables > Actions > New repository secret
# Name: ANTHROPIC_API_KEY
# Value: sk-ant-...Demo 32: 自动 Issue 分流
YAML 配置
创建 .github/workflows/claude-triage.yml:
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 读取 Issue 和代码库,然后分流
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__*实际效果
有人提交了一个 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.
2 分钟内,Claude:
- 搜索代码库中的邮箱验证逻辑
- 找到
src/auth/validators.ts中不处理+的正则表达式 - 检查
git log发现该正则在 3 天前的 PR #189 中被修改 - 添加标签:
bug、P1-high、auth - 发布评论:
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. |
+---------------------------------------------------------------+刚才发生了什么?
Demo 33: CI 安全门禁
目标
添加一个 CI 检查,在每个 PR 上运行,如果 Claude 在变更代码中发现安全漏洞,就阻止合并。与现有测试套件并行运行。
YAML 配置
创建 .github/workflows/claude-security.yml:
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."CI 输出效果
发现漏洞时(阻止合并):
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.安全门禁在 PR 上发布的评论:
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 }))扫描通过时:
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刚才发生了什么?
设为必需检查
在 GitHub 仓库设置中:
- 进入 Settings,然后 Branches,然后 Branch protection rules
- 编辑(或创建)
main的规则 - 启用 "Require status checks to pass before merging"
- 搜索 "Claude Security Scan" 并添加为必需检查
现在有 critical 级别安全漏洞的 PR 在问题解决前无法合并。
成本控制
安全扫描使用 claude -p(管道模式),以 diff 作为输入。这很高效:
- 只分析变更的代码,不是整个代码库
--max-tokens 8192限制响应成本- 默认使用 Sonnet(比 Opus 便宜)
- 每个 PR 平均成本:$0.02-0.15,取决于 diff 大小
对于大型仓库,添加文件数限制:
# Only scan the first 50 changed files
FILES=$(git diff --name-only origin/main...HEAD | head -50)组合使用:完整的 CI 流水线
三个工作流如何配合:
# .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: 审查 PR
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: 安全门禁(critical 发现时阻止合并)
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: 分流新 Issue
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: 响应 PR 评论中的 @claude 提及
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 }}组合流水线在 GitHub Actions 标签页中的样子:
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)常见问题排查
CI Token 权限不足
症状:工作流在尝试发布 PR 评论或添加标签时报 403 错误。
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.根因:工作流 YAML 中的 permissions 块缺失或不完整。GitHub Actions 默认使用受限 token。
修复:在工作流中添加必需的权限:
permissions:
contents: read # Read repo files and diffs
pull-requests: write # Post PR review comments
issues: write # Add labels, post issue comments如果你使用 fine-grained personal access token 而非默认的 GITHUB_TOKEN,确保它有:
- 仓库访问权限:Contents (read)、Pull requests (write)、Issues (write)
- 对于组织仓库:token 必须经组织管理员批准
API 速率限制
症状:安全扫描或审查间歇性地因速率限制错误失败。
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.根因:多个 PR 同时打开,或工作流在 PR 分支每次 push 时都重新触发,耗尽了 API 速率限制。
修复——减少触发频率:
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修复——添加并发控制:
concurrency:
group: claude-review-${{ github.event.pull_request.number }}
cancel-in-progress: true # Cancel previous runs when new commits are pushed修复——为瞬时故障添加重试逻辑:
- 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成本控制失败
症状:你每月的 Anthropic API 账单因 CI 使用而意外偏高。
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)根因和修复:
- 没有 max_tokens 限制:没有
--max-tokens,Claude 可能对大 diff 生成非常长的响应。
# Always set max_tokens in CI
max_tokens: 8192 # For claude-code-action
--max-tokens 8192 # For claude -p- 使用 Opus 而不是 Sonnet:Opus 每 token 费用大约是 Sonnet 的 15 倍。对于自动化审查,Sonnet 通常就够了。
model: claude-sonnet-4-6 # Not claude-opus-4-7- 每次 push 都运行:如果开发者向 PR 分支 push 了 10 次 commit,审查就运行 10 次。
# Use concurrency to cancel previous runs
concurrency:
group: claude-${{ github.event.pull_request.number }}
cancel-in-progress: true- 没有文件过滤:扫描文档或配置变更浪费 token。
# 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)- 设置预算告警:在 Anthropic 的 API 仪表盘配置支出超过阈值时的告警。
成本估算公式:
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 totalANTHROPIC_API_KEY Secret 未找到
症状:工作流因 API 密钥不可用而立即失败。
Run Claude Code Review ........ FAILED
Error: ANTHROPIC_API_KEY is not set.
Please add your API key as a repository secret.修复:
# 1. 前往你在 GitHub 上的仓库
# 2. Settings > Secrets and variables > Actions
# 3. 点击 "New repository secret"
# 4. Name: ANTHROPIC_API_KEY
# 5. Value: 你的 Anthropic API 密钥(以 sk-ant- 开头)对于组织仓库:Secret 必须在组织级别设置,或者仓库必须被授权访问组织 secret。
对于 fork 仓库:GitHub Actions 不会将 secret 传递给来自 fork 的 pull request 触发的工作流。这是一个安全特性。如果你需要审查 fork PR,使用 pull_request_target 替代 pull_request,但要注意安全影响。
知识检测
本章小结
claude-code-action以最少的配置处理 PR Review 和 Issue 分流——放入 YAML 就能工作- CI 脚本中的
claude -p给你完全的控制力,用于自定义分析和质量门禁 - 安全门禁通过在 critical 发现时以退出码 1 退出来阻止合并
- 成本控制:使用 Sonnet、限制 max tokens、添加并发控制,只分析变更的文件(不是整个仓库)
- 三种模式(review、triage、@-mention 响应)可以从单个工作流文件运行
- CI token 权限必须在工作流 YAML 中显式声明
- 速率限制和成本飙升是最常见的运维问题——从第一天就添加并发控制和预算告警
延伸阅读:A05 工具调用内部机制 解释了驱动 CI 中
claude -p的 JSON Schema 工具选择机制。A12 安全与对齐 讨论了为什么 CI 中的bypassPermissions需要受限 runner 环境和审计日志等补偿性控制。