让 Claude 半夜自己审 PR:Headless 模式 + GitHub Actions 实测

一、问题:交互式 Claude 没法挂到流水线上

人工 Code Review 跟不上 PR 速度,交互式调用 Claude 又没法自动化,怎么办?我们团队前阵子被这事卡住------夜里合了十几条 PR,第二天早会一堆"谁来 Review"的尴尬。

顺便一提,我维护的雷达鸭 App(收录中国一人公司赚钱案例,华为应用市场 + 微信小程序,Uni-app + arkTS + UniCloud)攒到一定体量后,PR 审查负担也压上来了,这才逼着我去翻 Claude Code 的 Headless 模式。

核心转变一句话:把"人确认"换成"参数约束"。交互式下,工具要不要执行、文件要不要改,都是人按回车;Headless 下,所有决策都靠参数预先约束好,Claude 自己跑完输出结果。

最朴素的调用:

bash 复制代码
# 最基本的 Headless 调用
claude -p "分析 src/ 目录的安全漏洞"

# 等价的完整形式
claude --print "分析 src/ 目录的安全漏洞"

-p 就是 --print 的短写,跑完直接退出,不进交互式 REPL。这就是无人值守的起点。

二、4 维参数:把交互式的"确认"翻译成约束

Headless 不是简单加个 -p,而是要把交互式里那些隐式约定全显式化。Claude Code 给了 4 个维度:

bash 复制代码
# 维度一:输出格式
claude -p "列出主要问题" --output-format text      # 纯文本,人看
claude -p "审查 PR" --output-format json           # 结构化,机器解析
claude -p "分析代码" --output-format stream-json   # 流式,实时监控

# 维度二:成本与规模护栏
claude -p "审查代码" --max-turns 5                 # 最多 5 轮工具调用
claude -p "分析日志" --max-budget-usd 0.50         # 单次硬上限 0.5 美元

# 维度三:安全边界
claude -p "审查代码变更" --allowedTools "Read,Grep,Glob"   # 只读,最安全
claude -p "生成文档" --disallowedTools "Bash"             # 黑名单禁 Bash
claude -p "检查 git 历史" \
  --allowedTools "Read,Grep,Glob,Bash(git log *),Bash(git diff *)"  # 细粒度白名单

# 维度四:执行控制
claude -p "检查代码格式" --model claude-haiku-4-5
claude -p "安全漏洞分析" --model claude-sonnet-4-6
claude -p "代码审查" --model claude-sonnet-4-6 --fallback-model haiku  # 主模型过载自动切

text 输出适合人看;json 给 CI 后续步骤消费;stream-json 一行一个事件,适合长任务实时盯。我们做 PR 审查统一用 json,因为后面要用 jq 提取 result 字段、用 total_cost_usd 算账。

json 输出长这样:

json 复制代码
{
  "type": "result",
  "subtype": "success",
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "is_error": false,
  "duration_ms": 12345,
  "num_turns": 5,
  "total_cost_usd": 0.0342,
  "usage": {
    "input_tokens": 5000,
    "output_tokens": 1500,
    "cache_read_input_tokens": 3000
  },
  "result": "发现 2 个 Critical 问题:\n1. SQL 注入风险..."
}

session_id 留着后面会话管理用,total_cost_usd 直接是这次审查花了几美分。我们跑了两周,平均一条 PR 审查成本在 0.03~0.08 美元之间------比之前请同事加班喝的咖啡便宜多了。

三、Unix 管道:Claude 当命令行工具用

Headless 模式下 Claude 就是个标准 stdin/stdout 程序,能塞进任何 Unix 管道:

bash 复制代码
# 分析日志文件
cat server.log | claude -p "找出所有 500 错误并总结根因"

# 分析 git 变更
git diff HEAD~1 | claude -p "总结这次提交的变更,格式为 Conventional Commits"

# Claude 分析 → jq 提取 → 发邮件
claude -p "检查是否有安全漏洞" --output-format json | \
  jq -r '.result' | \
  mail -s "安全扫描报告" security@company.com

# grep 预过滤 → Claude 深度分析
grep -r "TODO" src/ | claude -p "将这些 TODO 按优先级分类"

末尾那个例子是我们日常在用的------先 grep 把范围收窄,再喂给 Claude 分类,比让 Claude 自己 Grep 整个仓库省 token 也省时间。

四、GitHub Actions 两种接法

官方给了两条路。

方式一:官方 Action,起步首选

yaml 复制代码
# .github/workflows/claude-review.yml
name: Claude PR Review

on:
  pull_request:
    types: [opened, synchronize, reopened]
  issue_comment:
    types: [created]

# 同一 PR 的新提交取消旧的运行------避免浪费
concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  review:
    runs-on: ubuntu-latest
    if: |
      github.event_name == 'pull_request' ||
      contains(github.event.comment.body, '@claude')
    permissions:
      contents: read
      pull-requests: write
      issues: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # 完整历史,用于 diff 分析
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          prompt: |
            审查这个 PR 的代码变更,重点检查:
            1. 安全漏洞(SQL 注入、XSS、敏感信息硬编码)
            2. 未处理的错误情况和边界条件
            3. 性能问题(N+1 查询、内存泄漏风险)
          claude_args: >-
            --allowedTools "Read,Grep,Glob"
            --max-turns 10
            --model claude-sonnet-4-6

十几行 YAML 跑起来,PR 一开自动评论。concurrency 那段必加------同一 PR 连推几个 commit,旧的运行会被自动取消,不然账单会很难看。

方式二:直接 CLI,精细控制

需要自己处理变更文件列表、自己写评论、自己卡门禁的场景,用直接 CLI:

yaml 复制代码
name: Advanced AI Review

on:
  pull_request:
    types: [opened, synchronize]
    paths: ['src/**']    # 只在 src 目录变更时运行

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - 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)
          echo "files=$(echo "$FILES" | tr '\n' ' ')" >> $GITHUB_OUTPUT
          echo "count=$(echo "$FILES" | wc -l)" >> $GITHUB_OUTPUT
      - name: Run Claude Review
        id: review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
        run: |
          claude -p "审查以下 PR 变更文件:
          ${{ steps.changed.outputs.files }}

          重点检查安全漏洞、逻辑错误、性能问题。
          对每个问题给出文件名和行号。" \
            --output-format json \
            --max-turns 10 \
            --max-budget-usd 0.50 \
            --model claude-sonnet-4-6 \
            --allowedTools "Read,Grep,Glob" > review.json

          RESULT=$(jq -r '.result' review.json)
          COST=$(jq -r '.total_cost_usd' review.json)
          echo "cost=$COST" >> $GITHUB_OUTPUT
          echo "result<<EOF" >> $GITHUB_OUTPUT
          echo "$RESULT" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT
      - name: Post Review Comment
        uses: actions/github-script@v7
        with:
          script: |
            const cost = '${{ steps.review.outputs.cost }}';
            const result = `${{ steps.review.outputs.result }}`;
            await github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `## 🤖 Claude Code Review\n\n${result}\n\n---\n*Cost: $${cost} | Automated by Claude Code*`
            });
      - name: Gate on critical issues
        run: |
          RESULT="${{ steps.review.outputs.result }}"
          if echo "$RESULT" | grep -qi "critical"; then
            echo "::error::Critical issues found"
            exit 1
          fi

末尾那段 Gate on critical issues 是真有用的------评审里出现 critical 字样直接让 CI 失败,比让人盯着看评论靠谱。

两种方式的取舍:起步用方式一,几分钟接上;要卡门禁、要拿 total_cost_usd 做账、要把结果写到外部系统,就上方式二。我们团队最终留了方式二,因为要把每次审查成本塞进 Grafana。

五、多阶段 CI 管道:分析 → 修复 → 验证

单步审查只是开始。真正省事的是把 Claude 拆成多个 job,按阶段跑、按模型分级:

yaml 复制代码
name: Multi-Stage AI Pipeline

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  # 阶段一:快速格式检查(用便宜的模型)
  lint-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Format Check
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
        run: |
          npx @anthropic-ai/claude-code -p "检查 src/ 下的代码是否符合项目编码规范" \
            --model claude-haiku-4-5 \
            --max-turns 3 \
            --max-budget-usd 0.05 \
            --allowedTools "Read,Grep,Glob" \
            --output-format text

  # 阶段二:深度安全审查(用最强模型)
  security-scan:
    runs-on: ubuntu-latest
    needs: lint-check  # 格式检查通过后再做安全扫描
    steps:
      - uses: actions/checkout@v4
      - name: Security Scan
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
        run: |
          npx @anthropic-ai/claude-code -p "深度安全扫描,重点检查 OWASP Top 10" \
            --model claude-sonnet-4-6 \
            --append-system-prompt "你是应用安全专家,只关注安全漏洞。" \
            --max-turns 10 \
            --max-budget-usd 1.00 \
            --allowedTools "Read,Grep,Glob" \
            --output-format json \
            --json-schema '{"type":"object","properties":{"vulnerabilities":{"type":"array","items":{"type":"object","properties":{"severity":{"type":"string"},"description":{"type":"string"},"file":{"type":"string"}},"required":["severity","description"]}}},"required":["vulnerabilities"]}' \
            > security-report.json

阶段一用 Haiku(便宜、快),阶段二才上 Sonnet(贵、准)。needs: lint-check 让安全扫描等格式检查通过再跑------格式都过不了的 PR 没必要烧 Sonnet 的钱。这种"分级模型 + 阶段依赖"组合下来,我们一条 PR 的平均成本从单一 Sonnet 全量审查的 ~0.15 美元压到了 ~0.06 美元。

六、流式输出和会话管理

流式输出盯长任务

stream-json 一行一个事件,配 while read 能实时看 Claude 在干啥:

bash 复制代码
claude -p "分析整个 src/ 目录的架构问题" \
  --output-format stream-json \
  --allowedTools "Read,Grep,Glob" | while read -r line; do
    TYPE=$(echo "$line" | jq -r '.type // ""')
    case "$TYPE" in
      "system")   echo "[INIT] 模型: $(echo "$line" | jq -r '.model // "unknown"')" ;;
      "assistant") TEXT=$(echo "$line" | jq -r '.message.content[0].text // ""'); [ -n "$TEXT" ] && echo "[CLAUDE] $TEXT" ;;
      "result")   COST=$(echo "$line" | jq -r '.total_cost_usd // 0'); TURNS=$(echo "$line" | jq -r '.num_turns // 0'); echo "[DONE] 完成!$TURNS 轮,费用: \$$COST" ;;
    esac
done

我们把这个管道接到 CI 日志里,长任务不再是黑盒等 5 分钟------能实时看到 Claude 读到哪个文件、下了什么结论。

会话管理跨步骤复用上下文

多阶段管道每个 job 是独立容器,默认上下文丢光。--resumesession_id 能把上一轮分析接上:

bash 复制代码
# 第一次调用:分析代码结构
RESULT=$(claude -p "分析 src/ 的模块依赖关系" \
  --output-format json \
  --allowedTools "Read,Grep,Glob")

# 提取 session_id
SESSION_ID=$(echo "$RESULT" | jq -r '.session_id')

# 第二次调用:在上一轮分析的基础上继续
claude -p "基于刚才的分析,指出循环依赖问题" \
  --resume "$SESSION_ID" \
  --output-format text

# 继续当前目录最近的会话(不需要知道 session_id)
claude -p "继续刚才的分析" --continue

# 创建分支会话(保留原始会话不变)
claude -p "如果改用微服务架构呢?" --resume "$SESSION_ID" --fork-session

# 禁用会话持久化(适合完全无状态的 CI 场景)
claude -p "一次性分析" --no-session-persistence

--continue 不用记 session_id,直接续最近的;--fork-session 适合"假设性提问"------分支会话不影响主线。完全无状态的 CI 用 --no-session-persistence,跑完不落盘。

七、五层安全防线

让 Claude 半夜自己跑,最让人睡不着的就是"它会不会偷偷 rm -rf"。书里给的五层防线我们一层没少:

层一:最小权限 --------allowedTools 把工具收成白名单,审查任务只给 Read,Grep,Glob,连 Edit 都不给。

层二:Secrets 管理 ------API Key 一律走 ${{ secrets.ANTHROPIC_API_KEY }},绝不硬编码。硬编码会原样出现在 Actions 日志里,谁都能看。

层三:容器隔离------只读文件系统 + 无网络:

yaml 复制代码
jobs:
  review:
    runs-on: ubuntu-latest
    container:
      image: node:20
      options: --read-only --tmpfs /tmp --network none

--network none 直接断网,Claude 想外传数据也没出口。

层四:成本防护------路径过滤 + 并发限制 + 单次预算兜底:

yaml 复制代码
on:
  pull_request:
    paths:
      - 'src/**'
      - '!src/**/*.test.*'  # 排除测试文件
concurrency:
  group: claude-${{ github.event.pull_request.number }}
  cancel-in-progress: true

加上 --max-budget-usd 0.50,单次跑超 0.5 美元自动停。

层五:审计日志 ------JSON 输出天然带审计字段,tee 落盘:

bash 复制代码
claude -p "任务" --output-format json | tee -a /var/log/claude-audit.jsonl

事后谁、什么时候、花了多少钱、跑了多少轮,全在日志里。

八、CI 环境变量与完整模板

CI 里这几个环境变量必设:

bash 复制代码
ANTHROPIC_API_KEY=sk-ant-...                         # API 密钥
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1           # 一个变量关掉自动更新、遥测、错误上报
DISABLE_AUTOUPDATER=1                                # 禁用自动更新(CI 中必须)
DISABLE_TELEMETRY=1                                  # 禁用遥测
DISABLE_ERROR_REPORTING=1                            # 禁用错误上报
BASH_DEFAULT_TIMEOUT_MS=120000                       # Bash 默认超时 2 分钟
CLAUDE_CODE_MAX_OUTPUT_TOKENS=32000                  # 最大输出 token

CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 这个一定要加------不然 CI 跑着跑着 Claude 自己开始检查更新,日志里一堆噪音。

一个能直接抄的完整模板:

yaml 复制代码
- name: Run Claude Analysis
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
    CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
    BASH_DEFAULT_TIMEOUT_MS: "300000"
  run: |
    npx @anthropic-ai/claude-code -p "$PROMPT" \
      --output-format json \
      --max-turns 10 \
      --max-budget-usd 1.00 \
      --allowedTools "Read,Grep,Glob" \
      --model claude-sonnet-4-6 \
      --fallback-model haiku \
      --append-system-prompt "遵循项目 CLAUDE.md 中的审查规范。"

--fallback-model haiku 是个容易被忽略的小细节------主模型过载时自动切 Haiku,不至于让整条管道挂在 429 上。

九、判断

跑了两周,我们的结论是:Headless 模式不是"凑合用"的替代品,是 CI 集成的正解。交互式适合人盯着改代码,Headless 适合挂流水线------两个场景,两套约束逻辑。

反正我们以后的 PR 审查默认走 Claude + GitHub Actions,人只看 critical 项。半夜合的 PR,第二天早会前报告已经躺在评论里了。


关于作者:雷达鸭 App 独立开发者,10+ 年软件开发经验,软件设计师、人工智能应用工程师,专注鸿蒙 ArkTS + Web 前端,探索 AI 自动化。

版权声明:本文采用 MIT 协议开源,转载请注明出处。

相关推荐
春风野草6 小时前
AI Agent 跑长任务,不能只靠一个 async:AgentTask 和 AgentStep 状态机实战
aigc·ai编程
leeyi6 小时前
多 Agent 编排:Eino ADK 的三种协作模式(第57篇-E43)
aigc·agent·ai编程
kisbad7 小时前
Day 012|Embedding 和向量数据库:知识库检索到底在检什么
数据库·python·embedding·agent
五柳7 小时前
八大 Agent SDK 架构拆解:你的下一个 Agent 该选谁?
ai编程
用户593608741407 小时前
AI Harness 最佳实践
llm·agent
Qt历险记8 小时前
AI Agent 核心组件
ai编程
深蓝AI8 小时前
Capital One 开源 VulnHunter:AI 自动扫描代码漏洞,开发者安全实战指南
aigc
思考着亮8 小时前
7.agent
agent
一个处女座的程序猿9 小时前
Agent之Skill:dashi-ppt-skill的简介、安装和使用方法、案例应用之详细攻略
powerpoint·agent·ppt-skill