首页 / AI工具 / Claude Code CI/CD 集成实战:用 GitHu...

Claude Code CI/CD 集成实战:用 GitHub Actions 打造 7x24 小时自动代码审查与修复流水线

Claude Code CI/CD 集成实战:用 GitHub Actions 打造 7x24 小时自动代码审查与修复流水线

Claude Code CI/CD 集成实战:用 GitHub Actions 打造 7x24 小时自动代码审查与修复流水线

当 PR 积压到第三天,你还相信"代码审查是质量守护者"吗?本文带你用 Claude Code + GitHub Actions 搭建一条能自动审查、自动修复、自动汇报的流水线,让 AI 先把机械性问题挡在门外,让人工聚焦真正重要的架构决策。

一、引言:为什么 CI/CD 里需要 AI 代码审查

代码审查是工程团队公认的质量防线,但在实际执行中,它往往沦为瓶颈:

把 AI 代码审查嵌入 CI/CD 流水线,并不是要取代人工,而是重新分工:让 AI 在 PR 提交的几秒内完成第一轮筛查,过滤掉机械性问题、给出修复建议,甚至直接提交修复补丁;让人工审查者把注意力留给架构设计、业务正确性和边界场景。

为什么选 Claude Code?

本文将采用"直接调用 CLI + 自定义脚本"的方式,因为它对审查逻辑、报告格式和成本控制的掌控力最强,最适合学习与定制。


二、环境准备

2.1 前置条件

| 依赖 | 版本要求 | 说明 |

|------|---------|------|

| GitHub 仓库 | - | 需开启 Actions 功能 |

| Anthropic API Key | - | 在 console.anthropic.com 创建,注意设置用量上限 |

| Node.js | 18+ | Claude Code CLI 依赖 |

| Python | 3.10+ | 审查脚本运行环境(标准库即可,无需额外安装) |

2.2 配置仓库 Secrets

进入 GitHub 仓库 → Settings → Secrets and variables → Actions → New repository secret,添加:

2.3 本地验证 Claude Code CLI

在接入 CI 前,先在本地确认 CLI 可用:

# 安装 Claude Code CLI
npm install -g @anthropic-ai/claude-code

# 设置 API Key(本地测试用,CI 中走 Secret)
export ANTHROPIC_API_KEY="sk-ant-xxxxx"

# 冒烟测试:以 JSON 格式输出
claude -p "用一句话解释什么是单调函数" --output-format json --max-turns 1

若终端返回一段包含 resultcost_usd 字段的 JSON,说明环境就绪。


三、GitHub Actions Workflow 配置

下面是一份可直接使用的完整 workflow。它会在 PR 打开或更新时触发,安装依赖、跑审查脚本、把结果作为评论贴回 PR。

# .github/workflows/claude-review.yml
name: Claude Code Review

on:
  pull_request:
    types: [opened, synchronize, reopened]
    # 可选:只审查特定路径,节省成本
    # paths:
    #   - "src/**"
    #   - "*.py"
  workflow_dispatch: # 支持手动触发,便于调试

# 限制同一 PR 的并发运行,避免重复计费
concurrency:
  group: claude-review-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  review:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    permissions:
      contents: read       # 读取代码
      pull-requests: write # 发表 PR 评论
      issues: write        # 评论本质上走 issue API

    steps:
      - name: 检出代码
        uses: actions/checkout@v4
        with:
          fetch-depth: 0   # 完整历史,确保 diff 计算准确

      - name: 安装 Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: 缓存 Claude Code CLI(加速安装、省时)
        uses: actions/cache@v4
        with:
          path: ~/.npm-global
          key: claude-code-cli-${{ runner.os }}-v1
          restore-keys: |
            claude-code-cli-${{ runner.os }}-

      - name: 安装 Claude Code CLI
        run: npm install -g @anthropic-ai/claude-code

      - name: 设置 Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: 执行 AI 代码审查
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
          REPO: ${{ github.repository }}
        run: python scripts/claude_review.py

      - name: 上传审查报告为构件(便于归档追溯)
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: claude-review-report
          path: review_report.json
          retention-days: 14

关键设计说明:


四、Claude Code 自动审查脚本编写

这是整条流水线的核心。脚本完成三件事:取 diff → 调 Claude → 解析结果。代码使用 Python 标准库,无需安装第三方包。

#!/usr/bin/env python3
# scripts/claude_review.py
"""调用 Claude Code CLI 对 PR 变更进行自动审查,并输出结构化报告。"""

import json
import os
import subprocess
import sys
import urllib.request

# ---------- 1. 获取代码变更 ----------
def get_diff(base_sha: str, head_sha: str) -> str:
    """计算 base...head 之间的 diff,并做大小截断以控制成本。"""
    result = subprocess.run(
        ["git", "diff", f"{base_sha}...{head_sha}", "--unified=3"],
        capture_output=True, text=True, check=True,
    )
    diff = result.stdout
    # 单次审查的 diff 上限:约 3 万字符,超出则截断并提示
    MAX_DIFF_CHARS = 30000
    if len(diff) > MAX_DIFF_CHARS:
        diff = diff[:MAX_DIFF_CHARS] + "\n...(diff 过长已截断,请分批审查)\n"
    return diff


# ---------- 2. 调用 Claude Code 审查 ----------
def run_claude_review(diff: str) -> dict:
    """构造提示词并调用 claude CLI,返回解析后的 JSON。"""
    prompt = f"""你是一位严谨的资深代码审查专家。请审查下面的 Git diff,重点关注:
1. 潜在 Bug(空指针、越界、并发问题、资源泄漏)
2. 安全风险(注入、硬编码密钥、不安全的反序列化)
3. 性能隐患(N+1 查询、不必要的循环、内存浪费)
4. 可维护性(命名、复杂度、重复代码)

请严格只输出一个 JSON 对象,不要任何额外文字:
{{
  "summary": "一句话总体评价",
  "score": 1到10的整数,
  "issues": [
    {{
      "severity": "high | medium | low",
      "file": "文件路径",
      "line": 行号或0,
      "description": "问题描述",
      "suggestion": "修复建议"
    }}
  ]
}}

待审查的 diff:

{diff}

"""
    result = subprocess.run(
        [
            "claude", "-p", prompt,
            "--output-format", "json",
            "--max-turns", "1",        # 单轮,避免越界操作
        ],
        capture_output=True, text=True, check=True,
    )
    # claude --output-format json 返回的顶层结构包含 result 字段
    wrapper = json.loads(result.stdout)
    content = wrapper.get("result", "{}")
    # 去除模型可能包裹的 ```json 代码块标记
    content = content.strip()
    if content.startswith("```"):
        content = content.split("```")[1]
        if content.startswith("json"):
            content = content[4:]
    return json.loads(content)


# ---------- 3. 解析并落盘报告 ----------
def save_report(review: dict, wrapper: dict) -> None:
    """把审查结果与成本信息一并写入 review_report.json。"""
    report = {
        "review": review,
        "meta": {
            "cost_usd": wrapper.get("cost_usd"),
            "duration_ms": wrapper.get("duration_ms"),
            "num_turns": wrapper.get("num_turns"),
        },
    }
    with open("review_report.json", "w", encoding="utf-8") as f:
        json.dump(report, f, ensure_ascii=False, indent=2)


# ---------- 4. 主流程 ----------
def main() -> None:
    base_sha = os.environ["BASE_SHA"]
    head_sha = os.environ["HEAD_SHA"]

    diff = get_diff(base_sha, head_sha)
    if not diff.strip():
        print("没有检测到代码变更,跳过审查。")
        return

    wrapper = run_claude_review_wrapper(diff)
    review = json.loads(wrapper.get("result", "{}"))
    save_report(review, wrapper)
    print("审查完成,报告已写入 review_report.json")


def run_claude_review_wrapper(diff: str) -> dict:
    """封装一次调用,返回 claude 的完整 JSON 包装。"""
    prompt = f"""你是一位严谨的资深代码审查专家。请审查下面的 Git diff,重点关注:
1. 潜在 Bug
2. 安全风险
3. 性能隐患
4. 可维护性

请严格只输出一个 JSON 对象:
{{"summary":"一句话评价","score":1到10,"issues":[{{"severity":"high|medium|low","file":"路径","line":0,"description":"问题","suggestion":"建议"}}]}}

待审查的 diff:

{diff}

"""
    result = subprocess.run(
        ["claude", "-p", prompt, "--output-format", "json", "--max-turns", "1"],
        capture_output=True, text=True, check=True,
    )
    return json.loads(result.stdout)


if __name__ == "__main__":
    main()

说明:上面的 run_claude_reviewrun_claude_review_wrapper 在真实项目里可合并为一个函数。这里拆开是为了让"解析模型输出"和"获取原始包装"两步职责清晰,便于你按需裁剪。


五、自动修复流程

审查只是第一步,更有价值的是让 Claude 直接修复简单问题。我们通过一个独立的脚本 + workflow 步骤实现:给 Claude 授予文件编辑能力,让它改完代码后由 CI 自动提交一个修复 commit。

首先,新增一个修复脚本:

#!/usr/bin/env bash
# scripts/claude_autofix.sh
# 让 Claude Code 自动修复当前工作区的简单问题,并生成提交。
set -euo pipefail

BASE_SHA="${BASE_SHA}"
HEAD_SHA="${HEAD_SHA}"

# 取出变更文件清单,只让 Claude 处理本次改动的文件
FILES=$(git diff --name-only "${BASE_SHA}...${HEAD_SHA}" | grep -E '\.(py|js|ts|go|java)$' || true)
if [ -z "$FILES" ]; then
  echo "没有可自动修复的源码文件,退出。"
  exit 0
fi

# 调用 Claude,允许其使用文件读写与 bash 工具来完成修复
claude -p "以下文件在最近的提交中发生了变更,请逐一检查并修复其中的明显问题(如空指针、未处理异常、明显的逻辑错误)。只修改必要的部分,不要重写整段逻辑。修改完成后,简要说明你做了哪些改动。涉及文件:
$FILES" \
  --output-format json \
  --max-turns 8 \
  --allowedTools "Read,Edit,Bash(git diff:*)"

# 将 Claude 的修改提交为一个 fixup commit
git config user.name  "claude-code-bot"
git config user.email "bot@claude.local"
git add -A
if git diff --cached --quiet; then
  echo "Claude 未产生任何修改。"
else
  git commit -m "fix: Claude Code 自动修复(基于 AI 审查建议)"
  echo "已生成自动修复提交。"
fi

然后在 workflow 中新增一个条件触发的修复 job(注意与审查 job 分离,避免互相影响):

  autofix:
    needs: review
    runs-on: ubuntu-latest
    # 仅当审查报告里存在 high 级别问题时才自动修复(示例:可由前置 job 输出决定)
    if: github.event.action == 'opened'
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm install -g @anthropic-ai/claude-code
      - name: 运行自动修复
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
        run: bash scripts/claude_autofix.sh
      - name: 推送修复提交
        run: git push origin HEAD:${{ github.head_ref }}

安全提醒: 自动修复会直接 push 到分支。生产环境建议改为推到一个新的修复分支并自动开启 PR,由人工确认后再合并,避免 AI 直接污染主开发分支。


六、审查报告生成与 PR 评论

审查结果只有贴回 PR 才对开发者有用。下面的脚本读取 review_report.json,格式化为 Markdown,并通过 GitHub API 评论到 PR 上。

#!/usr/bin/env python3
# scripts/post_review_comment.py
"""读取审查报告,格式化为 Markdown 并评论到对应 PR。"""

import json
import os
import urllib.request

SEVERITY_EMOJI = {"high": "🔴", "medium": "🟡", "low": "🟢"}


def format_markdown(review: dict, meta: dict) -> str:
    lines = ["## 🤖 Claude Code 自动审查报告", ""]
    lines.append(f"**总体评价**:{review.get('summary', '无')}")
    lines.append(f"**质量评分**:{review.get('score', '-')}/10")
    lines.append(f"**本次成本**:${meta.get('cost_usd', 0):.4f} | "
                 f"耗时 {meta.get('duration_ms', 0)} ms")
    lines.append("")

    issues = review.get("issues", [])
    if not issues:
        lines.append("✅ 未发现明显问题,代码看起来不错!")
        return "\n".join(lines)

    lines.append(f"共发现 **{len(issues)}** 个潜在问题:\n")
    for i, issue in enumerate(issues, 1):
        sev = issue.get("severity", "low")
        icon = SEVERITY_EMOJI.get(sev, "⚪")
        lines.append(f"### {i}. {icon} [{sev.upper()}] "
                     f"{issue.get('file', '?')}:{issue.get('line', '?')}")
        lines.append(f"- **问题**:{issue.get('description', '')}")
        lines.append(f"- **建议**:{issue.get('suggestion', '')}")
        lines.append("")
    lines.append("---")
    lines.append("_由 Claude Code + GitHub Actions 自动生成,仅供参考。_")
    return "\n".join(lines)


def post_comment(repo: str, pr_number: str, body: str, token: str) -> None:
    url = f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments"
    data = json.dumps({"body": body}).encode("utf-8")
    req = urllib.request.Request(
        url, data=data, method="POST",
        headers={
            "Authorization": f"token {token}",
            "Accept": "application/vnd.github.v3+json",
            "Content-Type": "application/json",
        },
    )
    with urllib.request.urlopen(req) as resp:
        print(f"评论发布状态:{resp.status}")


def main() -> None:
    with open("review_report.json", encoding="utf-8") as f:
        report = json.load(f)
    body = format_markdown(report["review"], report.get("meta", {}))
    post_comment(
        repo=os.environ["REPO"],
        pr_number=os.environ["PR_NUMBER"],
        body=body,
        token=os.environ["GITHUB_TOKEN"],
    )


if __name__ == "__main__":
    main()

在 workflow 的审查 job 末尾追加一步即可调用它:

      - name: 将报告评论到 PR
        if: always()
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REPO: ${{ github.repository }}
        run: python scripts/post_review_comment.py

运行后,PR 上会出现一条带严重等级、文件定位和修复建议的评论,开发者可以直接对照修改。


七、性能优化与成本控制

AI 审查按 token 计费,如果不加控制,一个活跃仓库的月度账单可能令人吃惊。以下是几条经过验证的策略。

7.1 路径过滤,只审查真正的代码

在 workflow 触发条件中用 paths 过滤,避免文档、配置变更触发无意义的审查:

on:
  pull_request:
    paths:
      - "src/**"
      - "tests/**"
      - "!**/*.md"

7.2 diff 截断与分批

超大型 PR 一次性喂给模型既贵又容易超出上下文。前文脚本中的 MAX_DIFF_CHARS 截断是第一道防线;更成熟的做法是按文件分批调用,每批控制在合理 token 量内。

7.3 模型分级:简单问题用小模型

Claude Code 支持通过环境变量指定模型。对于纯格式、命名类问题,可切换到更轻量、更便宜的模型:

      - name: 执行 AI 代码审查(轻量任务用 Haiku)
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          ANTHROPIC_MODEL: "claude-haiku-4-5"   # 成本显著低于 Sonnet/Opus
        run: python scripts/claude_review.py

7.4 缓存依赖,减少安装耗时

前文已用 actions/cache 缓存 npm 全局目录。你也可以缓存 ~/.cache/claude 等运行时缓存目录,进一步缩短冷启动。

7.5 并发去重 + 成本看板

7.6 限制工具与轮次

审查任务用 --max-turns 1 且不授予写工具;修复任务才放开 Edit 并设 --max-turns 8。这样既省钱又安全——审查阶段不会发生意料之外的文件改动。


八、常见问题 FAQ

Q1:Claude Code 审查经常"幻觉"出根本不存在的问题,怎么办?

这是 LLM 审查的通病。缓解方法:(1) 在提示词里强制要求"只针对 diff 中实际出现的代码评论,不得臆测未展示的上下文";(2) 要求输出 JSON 时附带 fileline,并在评论脚本里校验该行号是否真实存在于 diff 中,过滤掉定位不到的条目;(3) 把严重级别阈值调高,只对 high 级别问题强提醒,medium/low 仅作参考。

Q2:workflow 报错 claude: command not found,但本地明明能跑。

CI 环境与本地不同。请确认:(1) setup-node 步骤在 npm install -g 之前执行;(2) 全局 npm bin 目录已加入 PATH,必要时显式 echo "$(npm config get prefix)/bin" >> $GITHUB_PATH;(3) 缓存键命中后,确认缓存的二进制确实兼容当前 runner 架构。

Q3:API 费用增长很快,怎么快速定位是哪个环节烧钱?

每条审查都会在 review_report.json 里记录 cost_usd。短期可以下载 artifact 人工汇总;长期建议在 workflow 末尾把单次成本通过 curl 上报到一个简单的统计服务(或写入一个专用 issue 的 comment),形成趋势图。同时开启路径过滤、diff 截断、小模型分级三件套,通常可降低 60% 以上成本。

Q4:自动修复把代码改坏了,如何回滚和预防?

首先,自动修复提交都是独立的 commit,git revert 即可回滚。预防层面:(1) 不要让 autofix 直接 push 到主开发分支,改为推到独立分支并开 PR;(2) 在修复提示词里明确"只修复确定性问题,遇到歧义保持原样";(3) 给 autofix job 配置必需的 CI 检查门禁,修复 PR 必须通过测试才能合并。

Q5:审查评论每次推送都刷屏,能否更新而不是追加?

可以。在 post_review_comment.py 中,先调用 GET /repos/{repo}/issues/{pr}/comments,查找上一条以"🤖 Claude Code 自动审查报告"开头的评论。若存在,改用 PATCH /repos/{repo}/issues/comments/{comment_id} 更新它;否则才 POST 新评论。这样每个 PR 始终只有一条最新的审查评论,干净清爽。

Q6:私有仓库能直接用官方 anthropics/claude-code-action 吗,和我自己写脚本有何区别?

能用,官方 Action 封装了认证、触发、PR 评论等常用能力,适合快速接入。自己写脚本的优势在于:完全掌控提示词、报告格式、成本统计与修复逻辑,便于和团队既有规范深度集成。建议先用官方 Action 跑通链路,再按需迁移到自定义脚本。

Q7:Claude Code 能审查非英文注释或中文命名的代码吗?

可以。Claude 对中文理解良好,审查中文注释和中文变量名没有障碍。若团队代码以中文为主,建议在提示词里注明"请用中文输出审查报告",并确保 review_report.json 以 UTF-8 写入(脚本中已设置 encoding="utf-8"),避免评论出现乱码。


九、总结

把 Claude Code 嵌入 GitHub Actions,本质上是给 CI/CD 流水线装上了一个"永不疲倦的初级审查员"。它的价值不在于替代人工判断,而在于:

本文给出的方案是一条可复制、可裁剪的基线:workflow YAML 负责编排,Python 脚本负责审查与报告,Bash 脚本负责修复,缓存与分级策略负责控成本。落地时,建议从"仅审查、不自动修复"起步,观察一段时间的报告质量与误报率,再逐步放开自动修复的权限边界。

AI 代码审查不是银弹,但当它和人工审查形成互补时,团队就能在质量与速度之间找到一个更舒服的平衡点。现在,就去你的仓库里加上第一个 .github/workflows/claude-review.yml 吧。