Towards Data Science

Don’t Let Claude Grade Its Own Homework

6.9内容质量

TL;DR · AI 摘要

Don’t Let Claude Grade Its Own Homework Towards Data Science Agentic AI Don’t Let Claude Grade Its Own Homework Cross-pr...

核心要点

  • 主题聚焦:Don’t Let Claude Grade Its Own Homework
  • 来源:Towards Data Science,建议结合原文判断细节。
  • AI 分析暂不可用,本条为保底评分与摘要。
#AI#编程#后端#安全#产品
打开原文

不要让Claude给自己批改作业 | Towards Data Science

Agentic AI

不要让Claude给自己批改作业

GitHub Actions中使用Codex进行跨供应商PR审查,以及为何来自不同实验室的第二意见比任何自我审查都更可靠

Ruben Broekx

2026年7月15日

25分钟阅读

分享

不久前,我让几个并行的Claude代理为我的一个副业项目撰写了一份策略文件夹。这个拉取请求看起来非常不错:有10个文档,全部整齐地相互链接,阅读体验良好。然后自动化审查者发布了它的发现结果,Claude诚实地表示没有异议:

Claude在会话中途承认:“自动化审查发现了一个真实的问题:文档作者在交叉链接时使用了猜测的文件名。这是一个有效的发现,我会修复它。”它撰写的文档链接指向了它自己虚构的文件。

完整的审查结果返回:95个相对链接中有30个指向不存在的文件。使用./market.md而不是market-analysis.md,使用./monetization.md而不是business-model.md,甚至有些文件完全是虚构的!如果作为人类快速浏览这个PR,我会批准它,因为每一个看起来都合理。幸运的是,我的自动化审查者发现了这个问题,因为它解析了每一个链接,而不是判断文件名是否“听起来合理”。

公平地说,Claude并没有恶意。语言模型只是生成最可能的延续,而有时最可能的延续是充满信心的虚构内容。但从阅读者的角度来看,这种区分几乎没有安慰作用。自信且错误的表述与自信且正确的表述看起来完全一样。

本文将介绍我用来帮助审查Claude错误和幻觉的系统:一个在GitHub Actions中运行的Codex代理,它会审查每一个拉取请求。它将带您了解我的设置方式,为何跨供应商审查优于自我审查和人工逐行审查,如何在审查循环中强制执行记忆和问责,我在过程中学到的成本和可靠性经验,以及我实际使用的代码。

代理吞吐量打破了人工审查循环

幻觉本身是一个可管理的问题,因为非常仔细的审查或功能评估可以发现它们。然而,过去一年真正崩溃的是需要审查的量。现在,单个工程师可以并行运行多个代理会话,每个会话在你重新倒一杯咖啡的时间里就能生成上千行的差异。生成代码已经成为快速且容易的部分;诚实地审查它们才是消耗整天的工作。这使得人工审查者成为整个交付流程的瓶颈,而瓶颈总是你应该优先自动化的下一个环节。

这个瓶颈带来了两种熟悉的失败模式。要么你批准代理生成的任何内容而没有真正阅读,导致断开的链接、幽灵参数和细微错误的边缘情况被部署到生产环境。要么你仍然坚持自己阅读每一行,结果你的快速且智能的AI代理反而比你做得更少。第一种是疏忽,第二种是浪费,大多数团队根据截止日期在两者之间摇摆。

我不断得出的结论是:我们的审查流程必须与代码生成速度同步扩展。人工参与仍然不可或缺,但人工审查者的关注点应聚焦于真正关键的部分。检查链接是否有效、新参数是否存在、是否存在漏洞或安全隐患、代码变更是否符合最初请求等重复性工作,必须由自动化流程在与代码生成代理相同节奏下完成,甚至在人类开始审查代码之前就完成这些工作。

洞察:幻觉是常态,吞吐量才是变化的关键。任何要求人类阅读每一行代码的流程都会将代理速度限制在人类阅读速度,因此首次审查必须完全自动化。

选择一个不与作者共享盲区的审查者

防止这些错误的显而易见的第一想法是让代理审查自己的工作……但这恰恰是最弱的方案。进入代码差异中的幻觉对生成它的模型来说,本质上是可信的。让相同权重的模型在相同偏见的编码会话中寻找代码错误,通常会重现让错误通过的相同盲区。

这种直觉有研究支持。Panickssery等人在NeurIPS 2024上展示,LLM评估者会识别并偏爱自己的生成结果:模型越擅长识别自己的输出,就越慷慨地给自己打高分。后续研究将这种自我偏好偏见与熟悉度联系起来:AI评判者对读起来可预测的文本评分更高,而对模型来说,没有比自己刚写出来的文本更可预测的了。

不同LLM提供商使用不同数据、不同训练配方和不同反馈循环进行训练。没有哪家提供商能完全消除错误,但模型产生的错误类型可追溯到其训练方式,而这种方式因提供商而异。这种去相关性正是你希望在审查代理中看到的。在我使用这种设置的一年里,OpenAI的Codex持续标记出Claude编写PR中Claude自身审查完全忽略的问题,从虚构的文件名到与初始请求完全无关的完整代码块,甚至直接引入安全违规。

你不能从这个结论中得出Codex是更优秀的工程师,应该让Codex而不是Claude来操作键盘。Codex也会频繁出现误判!只是方向与Claude不同。真正的力量在于多模型协作:在同一个工作流中结合不同提供商的模型,让模型互相弥补彼此的不足。

另一个错误结论是Codex的审查是完美的,可以盲目接受。审查中同样会发生幻觉,因此应将审查视为建议性意见。向作者(无论是Claude还是你自己)提出质疑,能将关注点引向潜在盲区,这正是需要深入聚焦和反思的完美信号。最终质量仍由作者负责,有时这意味着需要向Codex反馈其审查是错误的。关于这种机制的更多内容将在后文详细说明。

洞察:作者的错误对作者本身来说本质上是可信的,研究证实LLM对自身输出存在正向偏见。审查价值来自于去相关性,通过使用来自不同提供商、以不同方式失败的模型,正是这种差异能发现你遗漏的问题。

工作流程:一次LLM调用包裹在普通工程中

从概念上讲,一个LLM审查者可以用一行代码实现:“这里是代码差异,请进行审查”。OpenAI和Anthropic都提供了现成的快捷方式来实现这一点:OpenAI拥有一个GitHub审查集成,可自动审查PR;Anthropic则提供了claude-code-action。这些方案虽然可以作为起点,但出于对提示内容、审查生命周期、合并控制以及模型版本的自主管理需求,我更倾向于运行自定义的审查动作。当日常工作的核心变为管理与审查代理时,这些细节都至关重要。

具体来说,我使用的是仓库中包含的两个文件:

code
.github/
├── workflows/
│   └── pr.yml            # 编排配置:触发条件、上下文、重试机制与消息发布
└── actions/
    └── codex-pr-review/
        └── action.yml    # 审查逻辑:固定版本的Codex调用 + 审查提示模板

运行流程非常简洁。每当有PR被创建或收到推送时,pr.yml会检出代码、收集PR的完整对话历史,然后将这些内容传递给组合动作。该动作会对差异内容运行Codex,并将结果以单条消息形式返回。随后工作流会将这条消息作为持续更新的评论发布在PR对话中,同时发布一个状态标记:只要问题未解决,该标记会保持红色——这相当于与CI其他检查并列的“通过/不通过”信号。

整个流程的核心是一个看似简单的步骤,这行代码是唯一实际调用LLM的环节:

code
- uses: openai/codex-action@v1
  with:
    openai-api-key: ${{ inputs.openai-api-key }}
    prompt: |
      # 审查协议:定义审查范围、问题追踪方式
      # 以及反馈报告规范(详见下一部分解析)

其余所有配置都围绕着可靠执行这个步骤,并确保反馈能准确呈现在PR中。以下是完整的工作流配置:

code
name: PR Reviewer

on:
  pull_request:
    branches: [dev] # 所有开发工作均通过特性分支进行:feature -> dev -> main
    types: [opened, synchronize, reopened]

# 新推送会取消对前一个提交的在途(高成本)审查
concurrency:
  group: pr-reviewer-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  codex:
    if: github.event.pull_request.user.login != 'dependabot[bot]'
    runs-on: ubuntu-latest
    timeout-minutes: 15 # 一般审查耗时几分钟,15分钟完全足够
    permissions: # 该令牌只能读取代码和发布反馈,不能执行推送或合并操作
      contents: read
      issues: write
      pull-requests: write
      statuses: write
    steps:
      # /head引用会在每次推送后同步更新;
      # 自动生成的合并引用可能与检出操作产生竞争
      - uses: actions/checkout@v7
        with:
          ref: refs/pull/${{ github.event.pull_request.number }}/head

      # 确保本地存在两个差异端点,使Codex能解析base...head
      - name: 预获取PR的base和head引用
        run: |
          git fetch --no-tags origin \
            ${{ github.event.pull_request.base.ref }} \
            +refs/pull/${{ github.event.pull_request.number }}/head

反馈存在于三个地方:问题评论、审查和内联评论

  • name: 获取 PR 对话

id: conversation uses: actions/github-script@v9 with: result-encoding: string script: | const prNumber = context.payload.pull_request.number; const opts = { owner: context.repo.owner, repo: context.repo.repo }; const [comments, reviews, inline] = await Promise.all([ github.paginate(github.rest.issues.listComments, { ...opts, issue_number: prNumber }), github.paginate(github.rest.pulls.listReviews, { ...opts, pull_number: prNumber }), github.paginate(github.rest.pulls.listReviewComments, { ...opts, pull_number: prNumber }), ]); const all = [ ...comments.map(c => ({ date: c.created_at, text: @${c.user.login}: ${c.body}, })), ...reviews.filter(r => r.body).map(r => ({ date: r.submitted_at, text: @${r.user.login} (review ${r.state}): ${r.body}, })), ...inline.map(c => ({ date: c.created_at, text: @${c.user.login} (on ${c.path}): ${c.body}, })), ]; all.sort((a, b) => new Date(a.date) - new Date(b.date)); return all.map(e => e.text).join('\n---\n');

秘钥不会随 yml 文件传输,在缺少密钥时要发出明确提示

  • name: 缺失 OPENAI_API_KEY 时快速失败

env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | if [ -z "${OPENAI_API_KEY}" ]; then echo "::error::OPENAI_API_KEY 秘钥未在此仓库中设置。" exit 1 fi

重试包装器无法包裹 `uses:` 步骤,因此需要手动重试:短暂

故障(新运行器令牌的 401 错误,与 OpenAI 的短暂连接问题)

不应导致整个运行失败

  • name: 运行 Codex(第一次尝试)

id: codex_try1 uses: ./.github/actions/codex-pr-review continue-on-error: true with: openai-api-key: ${{ secrets.OPENAI_API_KEY }} conversation: ${{ steps.conversation.outputs.result }}

给短暂故障留出时间恢复后再重试

  • name: Codex 重试前等待

if: steps.codex_try1.outcome == 'failure' run: sleep 20

  • name: 运行 Codex(第二次尝试)

id: codex_try2 if: steps.codex_try1.outcome == 'failure' uses: ./.github/actions/codex-pr-review with: openai-api-key: ${{ secrets.OPENAI_API_KEY }} conversation: ${{ steps.conversation.outputs.result }}

  • name: 发布审查并发布状态

uses: actions/github-script@v9 env: REVIEW: >- ${{ (steps.codex_try1.outcome == 'success' && steps.codex_try1.outputs['final-message']) || steps.codex_try2.outputs['final-message'] }} with: script: | const MARKER = '<!-- codex-pr-review -->'; const opts = { owner: context.repo.owner, repo: context.repo.repo }; const prNumber = context.payload.pull_request.number; const raw = process.env.REVIEW; if (!raw) return; const body = ${MARKER}\n${raw};

javascript
// 一个规范注释,每次推送时都会就地编辑
const comments = await github.paginate(
  github.rest.issues.listComments, { ...opts, issue_number: prNumber });
const existing = comments.find(c =>
  c.user?.login === 'github-actions[bot]' && c.body?.includes(MARKER));
const posted = existing
  ? await github.rest.issues.updateComment(
      { ...opts, comment_id: existing.id, body })
  : await github.rest.issues.createComment(
      { ...opts, issue_number: prNumber, body });

// 是否通过:一个在发现项未解决时保持红色的提交状态
//(缺少尾部标记会被解读为"已提交评审",而非零发现项)
const m = raw.match(/<!--\s*codex-review:\s*unresolved=(\d+)/i);
const unresolved = m ? Number(m[1]) : null;
await github.rest.repos.createCommitStatus({
  ...opts,
  sha: context.payload.pull_request.head.sha,
  context: 'Codex review',
  state: unresolved > 0 ? 'failure' : 'success',
  description: unresolved === null
    ? '评审已提交,参见反馈摘要'
    : unresolved > 0
      ? `${unresolved} 个未解决发现项`
      : '所有发现项已解决',
  target_url: posted.data.html_url,
});

这些代码中的一些行需要特别关注,因为它们都是通过实际遇到的失败情况验证得出的:

  • 所有操作都在单个任务中执行。codex-action 的 README 中展示了一个双任务示例:第一个任务运行 Codex 并通过任务输出导出评审结果,第二个任务负责发布评审。按照文档建议我最初确实采用了这种方案,但随后开始收到空的评审。显然,GitHub 拒绝传递任何包含已注册密钥子字符串的任务输出(运行日志中一个被埋藏的警告信息是唯一线索),而且这个检查经常误报:大多数类似哈希的字符串都足够接近凭证,即使 Codex 会将其作为占位符包含在报告中。荒谬的是,这种屏蔽实际上毫无保护作用:被标记的"密钥"仍然以明文形式出现在第一个任务的日志中,唯一真正被隐藏的只是评审本身,它从未跨越任务边界。将所有操作保留在单个任务中可以完全规避这个检查,确保评审始终能到达 PR。
  • 评论抓取覆盖三个层面。问题评论、评审摘要和行内评论属于三个独立的 API,反馈可能出现在所有三个位置。无论抓取到什么内容,都会按时间顺序原封不动地传递给 Codex。这确保了 Codex 审查者能够获得正确的工作上下文。需要注意的一个限制是:对于长期存在的 PR,这个历史记录会持续增长,极端情况下可能需要进行裁剪或摘要处理。
  • 关键检查会快速失败。密钥信息不会与 yml 文件一起传输(也不应该被写入 yml 文件!)。将这个工作流复制到新仓库,忘记添加 OPENAI_API_KEY,你会在操作内部深处看到一个令人困惑的失败,而不是我们特意添加的单行错误信息来突出显示实际问题。
  • 手动尝试两次。重试包装器无法包裹步骤,因此我们的重试机制是首次尝试(出错时继续执行)加上一次受保护的第二次尝试。输出选择机制会根据第一次尝试的结果决定是否执行:如果直接使用原始回退方案(try1 || try2),可能会从中途失败的运行中获取到过时的不完整消息。
  • 该令牌采用最小权限原则。它只能读取代码、发布评论和设置状态,无法执行推送、批准或合并操作。无论审阅者还是作者代理遇到任何困惑,其影响范围都会在PR评论处终止。
  • 检出操作会固定refs/pull/N/head引用。GitHub会异步生成合并引用,而快速触发的工作流可能会与其产生竞争。/head引用会在每次推送时同步更新。紧随其后的预获取步骤确保两个差异端点在本地都存在:提示信息会将基础和头部SHA值直接传递给Codex,Codex会自行运行git命令进行差异计算。

洞察:模型调用只是简单部分。审阅者可靠性来源于围绕它的普通CI工程实践。

记忆的审阅优于重复的审阅

审阅者的职责是标注的页面。作者的职责是通过书面形式决定如何处理每个发现。使用ChatGPT制作。

大多数AI审阅者只会生成一次性评论:对某个时间点的差异发表即时意见,导致每次新推送时都产生额外的文字墙。提示合同的设置方式与这种方案不同,这也是我们审阅代理的核心所在:

code
name: Codex PR Review
description: 使用项目PR提示信息运行openai/codex-action。

inputs:
  openai-api-key:
    description: OpenAI API密钥。
    required: true
  conversation:
    description: 预获取的PR对话历史。
    required: true

outputs:
  final-message:
    description: Codex的审阅文本。
    value: ${{ steps.codex.outputs['final-message'] }}

runs:
  using: composite
  steps:
    - id: codex
      uses: openai/codex-action@v1
      with:
        openai-api-key: ${{ inputs.openai-api-key }}
        model: gpt-5.3-codex # 固定模型:默认值会浮动,你的账单也会随之浮动
        effort: medium # 每美元问题数的黄金平衡点
        prompt: |
          这是PR #${{ github.event.pull_request.number }},项目为${{ github.repository }}。
          基础SHA:${{ github.event.pull_request.base.sha }}
          头部SHA:${{ github.event.pull_request.head.sha }}

          仅审阅此PR引入的更改。提出改进建议、潜在漏洞和问题。简洁具体。
          使用markdown格式,每个问题/主题使用新标题。生成表格时要正确格式化,永远不要忘记表头分隔行。
          在表格单元格中,字面的`|`字符会破坏行:将每个竖线转义为`\|`,包括反引号内的竖线(写成 `` `curl ... \| bash` ``)。
          如果有疑问,重写单元格以完全避免使用竖线。分析对话历史以理解上下文和已提供的反馈。
          报告已修复的内容、遗漏的问题以及自上次以来引入的新问题。

如果维护者已针对某条反馈作出有理有据的决定,明确表示不会采取行动(例如拒绝、"不会修复"或解释该问题属于有意为之或超出范围),应将该问题视为已解决:在摘要表中标记为已解决,并附上一行说明其理由,且不要重新提出该问题。维护者的决定是最终决定:你的职责是提供建议,而非重新争论。只有在后续对PR的修改确实使维护者的理由失效时,才可重新提出该问题。

请按以下格式提交反馈:

  • ## Feedback Summary 部分:一个表格,记录该PR生命周期内所有曾提出过的反馈(列:IssueSeveritySummaryResolved),使用 ✅ 表示已解决,❌ 表示未解决,按严重程度排序,未解决项优先显示。
  • ## Open Issues 部分:仅包含未解决的问题,每个问题单独使用 ### Issue title [severity] 标题,结构包含 Issue:Proposed fix:Related files:
  • 最后添加一行机器可读内容:

<!-- codex-review: unresolved=N, high=M -->,其中 N 表示未解决项数量,M 表示严重程度为 High 的未解决项数量。即使 N 为 0 也必须包含该行,CI 会将其解析为状态检查。

提交请求标题和正文:


${{ github.event.pull_request.title }} ${{ github.event.pull_request.body }}

对话历史:


${{ inputs.conversation }}

code

此处有三个关键设计选择。

- 审查过程是状态化的。Feedback Summary 表格会跟踪PR在整个推送历史中所有曾提出过的发现(无论是否已解决)。结合工作流中的原地编辑评论,PR将携带一个完整生命周期的单一审查产物,而非五个反映审查过程更新的过时评论。无论是人类还是代理打开PR,都能一目了然地看到完整历史。

- 拒绝意见会被尊重。由于Codex会读取完整对话,作者可以书面拒绝某条发现,且该拒绝会跨审查周期持续有效。这正是使不完美的审查者仍能工作的机制:误报只需一次书面回复,而非无休止的催促循环,且异议会记录在案供后续读者参考。

- 审查结论是机器可读的。末尾的 `<!-- codex-review: unresolved=N, high=M -->` 行会被工作流解析为Codex审查提交状态。结果是每个PR获得两个独立信号:工作流检查告诉你审查已执行,状态则告诉你是否仍有待处理事项。红色状态表示存在未解决发现;绿色表示所有问题已修复或明确拒绝。我刻意将状态设为非阻塞,因为它仅用于通知。是否合并的决定仍保持开放,且由人类最终决定。

请注意,这种契约要求的不仅是拼写错误查找。Codex会将差异与PR声明的意图进行对比,按严重程度对每个发现进行排序,并附上具体的修复建议——这种审查方式能定期发现任何静态分析工具都无法生成的架构层面问题。

> 洞察:在审阅者中,结构优于原始模型质量。一个生命周期表、一个衰退规则和一个机器可读的结论,将自由形式的LLM意见转化为带有书面反驳机制的合并门,使误报变得廉价。

## 形成闭环:在你甚至查看之前,PR就已经变绿

从更大的工程图景来看,审阅者只是故事的一半。另一半是教会作者代理如何响应它,使审查往返过程不成为你的工作(记住:始终尝试消除瓶颈)。在我的仓库中,我使用了三个Claude代码技能来支持我,它们以纯markdown指令文件的形式编写在`.claude/skills/`目录下:

- `/pr-open` 创建 PR:分析分支的提交记录,本地运行验证,撰写诚实的标题和正文,并指定正确的基准分支。PR正文比看起来更重要,因为审阅者会将其视为它所审查的意图声明。我会让其明确说明完整的功能请求,并引用它所实现的Linear工单,这样审阅者和之后任何查看PR的人无需离开PR就能获得完整的背景信息。

- `/pr-iterate` 处理一次审查循环:扫描所有三个反馈渠道,然后将每个项目分类为修复、附带理由的拒绝或延期。修复会在本地实施并验证;拒绝会以PR评论的形式解释原因。审阅者是顾问性质的,一旦PR满足其声明的范围,“足够好以合并”就是一个有效的结论。

- `/pr-babysit` 是整个过程的看门人:监控CI,修复红色检查,在新反馈到达时运行一次迭代循环,并持续循环直到所有检查变为绿色且所有发现都被修复或拒绝。它的最终状态是“可合并”,当同一检查因相同根本原因失败两次时,它会停止报告而不是来回推送提交。

你和你的代理始终应记住的一个顺序规则:先回复,再推送。每次推送都会触发审查,并基于该时刻存在的上下文进行操作。任何尚未写下的内容(拒绝、修复摘要、你自己的评论)在该次审查循环中都不存在,审阅者会愉快地重新提出所有无法看到响应的项目。

最终状态是劳动分工的清晰划分。Claude撰写并辩护,Codex挑战并跟踪,循环持续运行直到PR变绿并附带干净的反馈表。我的队列中只包含已经可以合并的PR,每个PR都携带一条我可以一键审计的审查轨迹:发现了什么、修复了什么、以及拒绝了什么及其原因。

> 洞察:自动化审查的双方。当作者代理必须在推送前以书面形式修复或争辩每个发现时,人类队列中只包含带有可审计审查轨迹的可合并PR。

Codex审查状态变为红色。Claude确实实现了第一个发现,然后在PR评论中拒绝了第二个发现(真实评论,已匿名化):

已解决高优先级问题(数据层中硬编码的行数上限导致可避免的502错误):_fetch_capacity_grid现在会适应而不是失败。当遇到短页面(由于网格在恰好每天每人行数时密集,明确表示部署的上限截断了批次)时,它会将batch_size减半并重试相同切片,直到页面适配。只有当batch_size == 1时,短页面才会触发明显的502错误 [...] 关于第二个建议(将上限移至配置):故意未执行。真正的上限存在于数据库自身的托管API设置中,而非应用环境变量。将其镜像到应用配置会引入第二个数据源,可能导致与数据库静默漂移。自适应批处理完全消除了需要知道确切值的必要,这是更彻底的修复方案。现有的capacity-grid数据库测试仍通过;代码规范和类型检查均通过。

先回复,再推送,然后让Codex重新审查:

code
## 反馈摘要

| 问题                           | 严重程度 | 概要                          | 已解决 |
| ------------------------------- | -------- | -------------------------------- | -------- |
| 超出批次上限的截断           | 高       | 通过自适应批处理已修复。     | ✅       |
| 上限重复平台限制             | 中       | 拒绝:重复设置会导致漂移。   | ✅       |

<!-- codex-review: unresolved=0, high=0 -->

两行问题均已解决,一行通过修复,一行通过审查者接受的书面拒绝解决。第二行总结了整个机制:没有仅仅为了消除评论而实现冗余的变更,推理过程已作为PR历史的一部分供后续阅读者参考。

学习账本:成本与影响点

这套方案并非一蹴而就。其中大部分细节源于过程中的意外发现和死胡同,因此我按节省成本的粗略顺序记录这些经验,以免你重蹈覆辙:

  • 固定模型。codex-action的模型输入是可选的,留空表示“使用Codex当前默认模型”。我在账单上发现,默认模型在无声中切换到了更新但更昂贵的模型(gpt-5.5)。固定模型可解决此问题,但需要维护;目前我使用gpt-5.3-codex,该模型在撰写时已被标记为弃用(但API仍可用)。我会主动定期检查模型固定设置,而非被动接受默认值。
  • 将努力程度限制在中等。更高的推理努力能发现更多问题,但需要显著更多的金钱和时间投入。速度比表面看起来更重要:审查过程位于代理的迭代循环内部,其延迟会跨所有PR的每轮迭代累积,你自己的等待时间也是成本的一部分。中等努力程度在每美元发现的问题数和每分钟发现的问题数之间达到最佳平衡。不幸的是,没有哪种努力程度能覆盖所有问题,这就是人类负责人存在的意义。
  • 重试快速失败。新运行器令牌偶尔会401,API首次连接偶尔也会出现故障。两次尝试加受控回退机制可防止这些情况让工作流无故变为红色,这对代理(以及你自己的信任)将红色视为真实信号时尤为重要。
code

- 取消过时的审查。并发控制机制意味着只有PR的最新提交会接受审查。如果没有这个机制,当代理连续推送三次修复时,你会得到三次审查,其中两次是针对已不存在的代码。

- 审查应基于即将接收合并的基准分支。我的PR目标分支是dev,因此审查的差异对比对象是dev,我也会保持特性分支与该基准分支同步。如果分支过时,审查时会对比已发生变动的基准分支,导致报告出PR从未修改过的代码的虚假问题。

- 特性分支是前提条件。所有这些流程都依赖于pull_request事件。直接向dev或main分支推送会完全绕过审查者、状态检查和审计轨迹。每个变更对应一个分支的流程,才是让每个变更都能被审查的基础。

- 假设PR文本是不可信的输入。审查者读取的所有内容(PR正文、对话记录、代码本身)都可能成为提示注入的攻击面,而代理会使用你的API密钥在你的运行器上执行。最小权限令牌可以限制GitHub侧的潜在影响范围,codex-action则为运行器侧提供了沙箱化和安全策略的控制开关。同时需注意GitHub不会将敏感信息暴露给由forked PR触发的工作流,因此该模式假设来自可信贡献者的同仓库分支。在复制该模式到开源仓库前,请先审查信任模型。

## 现在你是总编

这条流水线的初衷从未是将人类排除在流程之外,其目的是提升你的能力。我不再是每个diff的首位阅读者,而是负责阅读审查结果、在两个模型出现分歧时进行裁决、并掌控写入审查提示中的标准。当Claude和Codex达成一致时,PR很可能没问题。当它们出现分歧时,这种分歧会精准指向需要我关注的代码。Claude有时会显得粗疏,Codex则倾向于过度设计,而由你自己把握这种平衡正是绝佳的位置。

审查审查结果(如此元)比以代理速度校对代理输出更高效,且能优雅降级。在懒散的日子里,我依靠审查轨迹合并绿色PR;在谨慎的日子里,我会查看反馈表并抽查拒绝项。无论如何,没有任何PR会仅凭一个模型的自信判断就触达合并按钮。

### 本文关键洞察

- 幻觉是常态,变化的是吞吐量。任何需要人类阅读每行代码的流程都会将代理速度限制在人类阅读速度,因此第一个审查环节必须自动化。

- 作者的bug对作者来说本质上是合理的,研究证实LLM对自身输出存在正向偏见。审查价值来自于解相关性,通过使用来自不同供应商且失败模式不同的模型,差异性正是发现你遗漏问题的关键。

- 调用模型是简单部分。审查者可靠性来自于围绕它的普通CI工程。

- 在审查者中,结构胜过原始模型质量。生命周期表、拒绝规则和机器可读的结论,将LLM的自由意见转化为带有书面异议机制的合并门禁,使误报成本低廉。

- 自动化审查的双方。当作者代理必须在推送前书面修复或争议每个发现,人类队列中永远只包含带有可审计审查轨迹的可合并PR。

> 最后一点建议:多模型协同工作。你的代理有时会向你提供自信的虚构内容,而不同供应商的模型可以互相弥补不足,因此你无需独自承担。在代理和主分支之间设置一个拥有记忆和合同的第二供应商审核者是一个不错的起点,但这并非唯一方式。每当某个模型的输出即将产生影响时,从不同实验室获取的第二意见是你能购买的最经济的保险。

在LinkedIn上关注我,获取简短的人工智能见解,在Towards Data Science上获取新文章的早期访问权限,或在Medium上查看完整存档。

作者

查看Ruben Broekx的全部文章

Ai Agent

,

claude

代码审查

Codex

深度解析

分享本文

- 在Facebook上分享

- 在LinkedIn上分享

- 在X上分享

Towards Data Science是社区出版物。提交你的见解以触达全球受众,并通过TDS作者支付计划获得收益。

更新为你的实际投稿URL

为TDS写作

✦ 结束CTA ✦