<!-- title: 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 工程师。

## 前置条件

- 已安装并跑通过本地 Claude Code（`claude` 命令可用，能正常对话）
- 一个 GitHub 仓库的写权限，能加 Actions Secrets
- 一把 Anthropic API Key（或 Claude 订阅 + OAuth，CI 场景推荐 API Key）
- 基本能看懂 YAML 和 `jq` 表达式（不会也能照抄）

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

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

- **`-p`（print/headless 模式）**：一次性接收 prompt、吐出结果、退出，不卡在 REPL 里等输入。这是 CI 能跑通的前提。
- **`--output-format stream-json`**：把"思考过程、工具调用、最终结论、花费、耗时"全部结构化成一行行 JSON。机器才好解析、好分类、好回写评论。
- **hooks 权限围栏**：CI 上 Claude 是拿真金白银的 API 配额在跑，也可能接触到生产配置。hooks 让你在"工具执行前/后"插钩子，危险操作直接拦死。

```mermaid
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 仓库，跑：

```bash
# 把改动 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 要的是后者：

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

**预期结果**：生成一个 `review.jsonl`，每行一个 JSON 事件。关键事件类型：

```jsonl
{"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}
```

- 末尾那条 `type:"result"` 是金矿：`result` 是最终答复全文，`cost_usd` 是本次花费，`num_turns` 是轮数。
- 用 `jq` 把结论和花费抠出来：

```bash
# 只取最终结论文本
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 拦危险操作：

```json
{
  "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`：

```yaml
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 在工作区内自由读文件（但禁写、禁执行危险命令）：

```bash
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 又贵。改成按变更文件批量喂：

```bash
# 拿到本次 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`（故意留两个雷）：

```python
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_
```

5. 因为有"SEVERITY: 高"，CI job 变红，PR 被拦截。修复后重推，重新跑，全 LGTM 才放行。

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

## 进阶：接官方 Action 还是自建？

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

## 延伸阅读

- **Claude Code 官方文档（headless / CLI）**：`https://docs.claude.com/en/docs/claude-code` —— `-p`、`--output-format`、`--permission-mode`、hooks 全谱。
- **stream-json 事件类型全表**：`https://github.com/badlogic/cc-wrap/blob/main/docs/claude-stream-json.md` —— 每种事件的字段细节，写解析器时当字典查。
- **官方 GitHub Action**：`https://github.com/anthropics/claude-code-action` —— 现成的 `@claude` 触发方案。
- **`claude-code-automation` 社区 skill**：`https://skills.rest/skill/claude-code-automation` —— headless + 流式封装，可作自建脚本的参考实现。

## 训练营适用性评估

- **适合**：DevOps / SRE / 平台工程方向的 1–2 天短训营；进阶"AI 工程化"营的 CI 集成模块。学员需先有本地 Claude Code 基础。
- **教学时长**：讲师演示 + 学员动手，约 **3–4 小时**。其中步骤 1–2（headless+JSON）1 小时、步骤 3（hooks）45 分钟、步骤 4–6（Actions+批量）1.5 小时、实战案例联调 45 分钟。
- **不适合**：完全没碰过 Git/CI 的零基础营；建议先修"入门篇"再做本主题。
- **考核点**：学员能独立开 PR、看到结构化审查评论、并解释 hooks 如何拦下一条危险命令。

## 参考来源

1. [Claude Code 官方文档](https://docs.claude.com/en/docs/claude-code) — `-p` headless 模式、输出格式、hooks、权限模式权威说明
2. [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) — Anthropic 官方 GitHub Action，`@claude` 触发与 PR 审查示例
3. [cc-wrap: claude-stream-json 事件参考](https://github.com/badlogic/cc-wrap/blob/main/docs/claude-stream-json.md) — stream-json 各事件类型字段的 exhaustive 参考
4. [Claude Code CLI 指南](https://skillsplayground.com/guides/claude-code-cli/) — 命令、flag、管道、输出格式的实操速查（2026）
5. [claude-code-automation skill](https://skills.rest/skill/claude-code-automation) — 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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
