首页 › tutorials › Claude Code入CI造自动PR审查机器人

概述

本教程教你把 Claude Code 的 headless(无头)模式接入 GitHub Actions,造一个"PR 一开就自动审查、高危问题直接拦 CI"的机器人。你会掌握 -p 非交互运行、--output-format stream-json 机器可读输出、hooks 权限围栏、--max-turns 成本控制,以及用 jq 把审查结论回写成 PR 评论的全链路。适合已用过本地 Claude Code、想把它搬上 CI/CD 流水线的开发者和 DevOps 工程师。

前置条件

核心思路:为什么是 headless + JSON + hooks 三件套

交互模式是给人看的,CI 是给机器跑的。三者各司其职:

flowchart TD
    A[PR opened/synchronize] --> B[Actions: checkout + 全量 diff]
    B --> C[拼接审查 prompt]
    C --> D["claude -p<br/>--output-format stream-json<br/>--permission-mode plan<br/>--max-turns 8"]
    D -. hooks 围栏 .-> G{PreToolUse 拦截?}
    G -- 危险命令 --> X[阻断 + 报警]
    G -- 安全 --> E[stream-json 流]
    E --> F[jq 提取 result + cost]
    F --> H{高危问题?}
    H -- 是 --> I[gh 评论 + 关评论 + job 失败]
    H -- 否 --> J[gh 评论 + 通过]

正文

步骤 1:本地先跑通 headless 审查

先在本地把一行命令调通,再搬上 CI。进到任意 Git 仓库,跑:

# 把改动 diff 喂给 Claude,让它只读审查、不给建议改代码
git diff origin/main...HEAD | claude -p \
  "你是资深 reviewer。审这段 diff,只报告:1)逻辑 bug 2)安全隐患 3)明显坏味道。
   每条用 'SEVERITY: 高/中/低 | 文件:行 | 问题 | 建议' 格式。没问题就回 'LGTM'。" \
  --output-format text \
  --permission-mode plan

预期结果:终端打印一段结构化文字,每行一条问题,或一句 LGTM。

关键点: - --permission-mode plan 让 Claude 进"计划模式"——只读、不动手改文件。CI 审查阶段你只要结论,不要它真去 Edit 代码。 - git diff 通过管道(stdin)喂进去,Claude 把 stdin 当 prompt 的一部分。这是 Unix 哲学的接入方式,能和任何上游命令组合。

常见问题: - 卡住不动:忘了 -p,进了交互 REPL。CI 里没有 TTY,会直接挂死。务必带 -p。 - 输出里夹了"好的,我来审查"之类废话:那是 text 格式。要纯净机器可读,下一步换 json/stream-json。

步骤 2:换 stream-json,拿到机器可读的结论

text 给人看,json/stream-json 给程序解析。CI 要的是后者:

git diff origin/main...HEAD | claude -p "同上 prompt" \
  --output-format stream-json \
  --input-format text \
  --verbose > review.jsonl

预期结果:生成一个 review.jsonl,每行一个 JSON 事件。关键事件类型:

{"type":"system","subtype":"init",...}
{"type":"assistant","message":{"content":[{"type":"text","text":"SEVERITY: 高 | ..."}]}}
{"type":"result","result":"<最终汇总文本>","cost_usd":0.0123,"duration_ms":8421,"num_turns":2,"is_error":false}
# 只取最终结论文本
jq -r 'select(.type=="result") | .result' review.jsonl

# 顺便打个成本小票
jq -r 'select(.type=="result") | "花费 $\(.cost_usd) / \(.num_turns) 轮 / \(.duration_ms)ms"' review.jsonl

常见问题: - jq 报 parse error:stream-json 每行一个完整 JSON(JSONL),别当成一个大 JSON 解析。用 jq -R 'fromjson? | ...' 更稳,能跳过空行。 - 想要 type-safe 的结构化结论:用 --output-format json 配合在 prompt 里要求"只输出符合下列 JSON Schema 的 JSON",或在 Agent SDK 里用 StructuredOutput(Zod/Pydantic 校验)。CI 简单场景 JSONL + jq 足够。

步骤 3:给 CI 配 hooks 权限围栏

CI 上 Claude 不能"想干啥干啥"。在仓库根目录建 .claude/settings.json,用 hooks 拦危险操作:

{
  "permissions": {
    "defaultMode": "plan",
    "deny": ["Bash(rm -rf *)", "Bash(git push *)", "Bash(curl * | sh)"]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.command' | grep -qiE 'rm -rf|sudo|git push|:(){:|:&};:' && { echo 'BLOCKED by CI hook'; exit 2; } || exit 0"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "echo \"[audit] $(date -u +%FT%TZ) review done\" >> /tmp/cc-audit.log" }
        ]
      }
    ]
  }
}

机制说明:hooks 的 command 从 stdin 收到一段描述本次工具调用的 JSON。exit 2 表示阻断该工具调用并反馈给 Claude;exit 0 放行。Stop 钩子在主 agent 结束时触发,这里用来写审计日志。

预期结果:Claude 试图跑 rm -rf node_modules 时被拦,CI 日志里出现 BLOCKED by CI hook。

常见问题: - 钩子没触发:确认 matcher 写对。PreToolUse 的 matcher 匹配的是工具名(Bash/Write/Edit),可用正则 Bash|Write。permissions.deny 是更轻量的硬黑名单,优先用它拦确定性的危险命令。 - exit 2 反馈给模型后它一直重试:在 deny 里把该命令彻底禁掉(硬禁止,不会触发重试),hooks 留给需要"看上下文再判断"的软规则。

步骤 4:搭 GitHub Actions 工作流

在仓库建 .github/workflows/claude-review.yml:

name: Claude PR Review

on:
  pull_request:
    types: [opened, synchronize, reopened]
  workflow_dispatch: {}

jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      issues: write
    concurrency:
      group: claude-review-${{ github.event.pull_request.number }}
      cancel-in-progress: true
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }   # 要全历史才能算 diff

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

      - name: Run review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          BASE="${{ github.event.pull_request.base.sha }}"
          HEAD="${{ github.event.pull_request.head.sha }}"
          git diff "${BASE}...${HEAD}" > pr.diff

          cat > /tmp/prompt.txt <<'EOF'
          你是资深 reviewer,审查附带的 PR diff。只报告:
          1) 逻辑 bug  2) 安全隐患  3) 明显坏味道
          每条格式:SEVERITY: 高/中/低 | path:line | 问题 | 修复建议
          没问题就只回一行:LGTM
          EOF

          cat /tmp/prompt.txt pr.diff | claude -p \
            --output-format stream-json \
            --input-format text \
            --permission-mode plan \
            --max-turns 6 \
            --add-dir "$GITHUB_WORKSPACE" \
            --verbose > review.jsonl

      - name: Post comment + gate
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_URL: ${{ github.event.pull_request.html_url }}
        run: |
          BODY=$(jq -rs '
            [ .[] | select(.type=="result") ][0]
            | .result as $r
            | "**🤖 Claude Code 自动审查**\n\n" + $r
            + "\n\n_成本: $\(.cost_usd) · 轮数: \(.num_turns)_"
          ' review.jsonl)

          echo "$BODY" | gh pr comment "$PR_URL" --body-file -

          # 高危即拦 CI
          if echo "$BODY" | grep -q "SEVERITY: 高"; then
            echo "::error::检测到高危问题,已拦截 PR"
            exit 1
          fi

预期结果:每次推 PR 或新提交,Actions 跑一轮,PR 上多一条机器人评论,列问题清单 + 成本;有"高危"时 job 红掉。

关键点: - concurrency 按合并同一 PR 的多次推送,旧运行自动取消——省 token。 - permissions 给到最小必要:pull-requests: write 才能发评论。 - ANTHROPIC_API_KEY 放仓库 Secrets,绝不能写进 yml。GITHUB_TOKEN 是 Actions 自动注入的,用来调 gh。

常见问题: - gh pr comment 403:permissions.pull-requests 没给 write,或 fork PR 默认不让跑 secrets。fork 场景需 pull_request_target + 严格校验,但那是双刃剑,本文末尾"注意事项"会点。 - diff 太大超 token:下面步骤 6 讲怎么按文件批量切。

步骤 5:让审查能"读上下文"而不只看 diff

只喂 diff 容易误报(看不到被调用函数的实现)。让 Claude 在工作区内自由读文件(但禁写、禁执行危险命令):

cat prompt.txt | claude -p \
  --output-format stream-json \
  --permission-mode plan \      # plan = 只读,天然禁写
  --add-dir "$GITHUB_WORKSPACE" \  # 允许访问整个仓库
  --allowedTools "Read Grep Glob Bash(git diff:*) Bash(git log:*)" \
  --max-turns 8

机制:--permission-mode plan 配合 --allowedTools 白名单,Claude 可以 Read/Grep 翻仓库理解上下文,但碰不到 Edit/Write。这是"既能读全貌、又改不了代码"的甜点配置。

预期结果:误报下降。评论里会出现"我查了 src/auth.ts:42,这个调用是安全的,因为……"这类有依据的判断。

步骤 6:大 PR 按文件批量切 + 成本控制

一个 5000 行的 PR,整段喂进去既超 token 又贵。改成按变更文件批量喂:

# 拿到本次 PR 改动的文件清单
FILES=$(git diff --name-only "$BASE...$HEAD")

for f in $FILES; do
  git diff "$BASE...$HEAD" -- "$f" | claude -p "审查此文件 diff,格式同上,无问题回 LGTM" \
    --output-format json \
    --permission-mode plan \
    --max-turns 4 \
    > "review_$(echo $f | tr / _).json" &
done
wait

# 汇总所有非 LGTM 的结论
jq -rs 'map(.result) | map(select(. != "LGTM")) | join("\n\n---\n\n")' review_*.json

成本护栏: - --max-turns N 是硬上限——单个文件审查最多走 N 轮就停。批量时把 N 调小(4 左右),防一个文件烧爆预算。 - 并行 & + wait 多文件同时审,省墙钟时间。但注意并发会同时吃多个请求的配额,按账号 rate limit 量力而行。

常见问题: - 并发把 API 打到 429:用 xargs -P 4 限并发到 4。 - 某个文件 JSON 解析失败:用 jq -R 'fromjson? // empty' 容错跳过坏行,别让一个文件炸掉整轮。

实战案例:一个可跑的最小仓库

目标:随便开个 PR,立刻得到 Claude 的结构化审查评论,高危直接红 CI。

仓库结构:

my-repo/
├── .github/workflows/claude-review.yml   # 步骤 4 的工作流
├── .claude/settings.json                 # 步骤 3 的 hooks 围栏
├── src/levy.py                           # 故意带 bug 的待审代码
└── README.md

src/levy.py(故意留两个雷):

def calc_total(items, coupon=None):
    total = sum(i['price'] for i in items)
    if coupon:
        total = total - coupon          # BUG1: 没校验 coupon 正负,负券直接白送
    return total

def save_user(data):
    import sqlite3
    db = sqlite3.connect('app.db')
    db.execute(f"INSERT INTO users VALUES ('{data['name']}')")  # BUG2: SQL 注入
    db.commit()

操作流程:

  1. 把上面的 .claude/settings.json 和 .github/workflows/claude-review.yml 放进仓库。
  2. 仓库 Settings → Secrets → 加 ANTHROPIC_API_KEY。
  3. 开个分支,加 src/levy.py,推上去,开 PR。
  4. 等 Actions 跑完,看 PR 评论。预期看到类似:
🤖 Claude Code 自动审查

SEVERITY: 高 | src/levy.py:4 | coupon 未做正数校验,传负数可白嫖 | 加 if coupon < 0 or coupon > total: raise
SEVERITY: 高 | src/levy.py:10 | 字符串拼接 SQL,存在注入 | 改用参数化 db.execute("... VALUES (?)", (data['name'],))
SEVERITY: 低 | src/levy.py:8 | import 写在函数内 | 提到模块顶部

_成本: $0.0118 · 轮数: 3_
  1. 因为有"SEVERITY: 高",CI job 变红,PR 被拦截。修复后重推,重新跑,全 LGTM 才放行。

这就完成了一个闭环:代码提交 → 自动审查 → 结构化反馈 → 高危拦截,全程无人值守。

进阶:接官方 Action 还是自建?

Anthropic 官方维护了 anthropics/claude-code-action,支持 @claude 评论触发、PR 审查、issue 处理。它省去了你自己写 prompt 和回写逻辑,适合"我要现成的"。本教程的自建路线胜在完全可控:prompt、输出格式、拦截规则、成本上限全在你手里,适合需要深度定制审查标准、或要把审查结论灌进别的系统(如 Slack、Jira)的团队。两者也可并存:官方 Action 做 @claude 交互式响应,自建 workflow 做每 PR 自动门禁。

延伸阅读

训练营适用性评估

参考来源

  1. Claude Code 官方文档 — -p headless 模式、输出格式、hooks、权限模式权威说明
  2. anthropics/claude-code-action — Anthropic 官方 GitHub Action,@claude 触发与 PR 审查示例
  3. cc-wrap: claude-stream-json 事件参考 — stream-json 各事件类型字段的 exhaustive 参考
  4. Claude Code CLI 指南 — 命令、flag、管道、输出格式的实操速查(2026)
  5. claude-code-automation skill — headless + 流式自动化封装,自建 workflow 的参考实现

本文由「51学通信」原创,站长爱卫生(微信 gprshome201101)。Claude Code 接 CI 是把 AI 从"开发者的副驾"升级成"团队的守门员"——每一行提交都有人盯,高危直接拦。后续我们将拆解 stream-json 事件流的深度解析,敬请关注。

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入