本教程教你把 Claude Code 的 headless(无头)模式接入 GitHub Actions,造一个"PR 一开就自动审查、高危问题直接拦 CI"的机器人。你会掌握 -p 非交互运行、--output-format stream-json 机器可读输出、hooks 权限围栏、--max-turns 成本控制,以及用 jq 把审查结论回写成 PR 评论的全链路。适合已用过本地 Claude Code、想把它搬上 CI/CD 流水线的开发者和 DevOps 工程师。
claude 命令可用,能正常对话)jq 表达式(不会也能照抄)交互模式是给人看的,CI 是给机器跑的。三者各司其职:
-p(print/headless 模式):一次性接收 prompt、吐出结果、退出,不卡在 REPL 里等输入。这是 CI 能跑通的前提。--output-format stream-json:把"思考过程、工具调用、最终结论、花费、耗时"全部结构化成一行行 JSON。机器才好解析、好分类、好回写评论。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 评论 + 通过]
先在本地把一行命令调通,再搬上 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。
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}
type:"result" 是金矿:result 是最终答复全文,cost_usd 是本次花费,num_turns 是轮数。jq 把结论和花费抠出来:# 只取最终结论文本
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 足够。
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 留给需要"看上下文再判断"的软规则。
在仓库建 .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 讲怎么按文件批量切。
只喂 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,这个调用是安全的,因为……"这类有依据的判断。
一个 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()
操作流程:
.claude/settings.json 和 .github/workflows/claude-review.yml 放进仓库。ANTHROPIC_API_KEY。src/levy.py,推上去,开 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_
这就完成了一个闭环:代码提交 → 自动审查 → 结构化反馈 → 高危拦截,全程无人值守。
Anthropic 官方维护了 anthropics/claude-code-action,支持 @claude 评论触发、PR 审查、issue 处理。它省去了你自己写 prompt 和回写逻辑,适合"我要现成的"。本教程的自建路线胜在完全可控:prompt、输出格式、拦截规则、成本上限全在你手里,适合需要深度定制审查标准、或要把审查结论灌进别的系统(如 Slack、Jira)的团队。两者也可并存:官方 Action 做 @claude 交互式响应,自建 workflow 做每 PR 自动门禁。
https://docs.claude.com/en/docs/claude-code —— -p、--output-format、--permission-mode、hooks 全谱。https://github.com/badlogic/cc-wrap/blob/main/docs/claude-stream-json.md —— 每种事件的字段细节,写解析器时当字典查。https://github.com/anthropics/claude-code-action —— 现成的 @claude 触发方案。claude-code-automation 社区 skill:https://skills.rest/skill/claude-code-automation —— headless + 流式封装,可作自建脚本的参考实现。-p headless 模式、输出格式、hooks、权限模式权威说明@claude 触发与 PR 审查示例本文由「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 等场景全覆盖。
