我们之前说过,Claude 编写了 Claude Code 自身约 90% 的代码。那么,在我们这样的产出规模下,我们如何审查代码?现在还有人读代码吗?如果出了问题,谁来发现?

本文介绍 Claude Code 团队的代码审查流程及其演变过程。从 6 月到 9 月,Claude Code 代码库合并的拉取请求数量增长了一倍多,而需要回滚的比例下降了 40% 以上。

我们并没有取消审查。但我们确实把大部分阅读工作交给了机器,以优化人类的时间和注意力,并大力投入让撤销错误变得快速而简单。

重新思考代码审查的目的

当智能体编写代码时,变更数量会快速上升。在 Anthropic 的 monorepo 中,每天落地的变更从 5 月的约2,500 次增加到 9 月的 30,000 多次(今年)。

Claude Code 位于自己独立的代码库中,与 monorepo 分开,但它也承受着很大压力。当你以这样的速度交付时,手动逐行审查代码极其困难,甚至不可能。

代码审查有两个作用:

  1. 在发布前发现并修复问题
  2. 在团队中建立共享上下文和对代码库的理解

本文主要关注发现和修复问题,但我们会在结尾回到第二个作用。

我们认为,“有人读过代码吗?”并不能准确代表“这个变更可能出什么问题,哪项检查——自动的还是人工的——才是发现它的正确方式?”

提出这个问题也改变了审查发生的时机。以前,审查是终点前的一道关卡:写代码、开 PR、等同事。

现在,审查持续进行,更像写作时的拼写检查。大多数变更在请人查看之前,已经至少被 Claude 审查并修复过一次。这能发现大多数常规错误,并把团队的注意力留给更高层次的问题和审视。

稀缺资源:人类的时间和注意力

我们希望把人类的时间和注意力留给只有人类才能解决的问题,并确保自动化检查能拦住其余的问题。以我们目前的变革速度,人类仍有大量工作要做,我们的团队几乎每天都会遇到新的、前所未有的挑战。

人类时间涵盖代码审查成本的两方面:人们花在审查上的时间,以及花在修复坏代码所破坏的东西上的时间。如果削减前者只是让时间出现在后者上,那就没有帮助。

因此目标是:Claude Code 各仓库(CLI、桌面端和 Web 应用以及移动应用)中的大多数拉取请求在合并时,参与循环的人类最多只有一位,且可以是作者本人,同时不增加回滚或事故。为了衡量人类时间,我们跟踪每个 PR 的人类批准次数(中位数和第 95 百分位数)、人类评论数,以及 PR 等待某个人的时长。回滚率和事故率是护栏:如果它们上升,我们就没有节省人类时间,只是把时间挪了地方。

一次变更的生命周期

一次典型的变更,从工程师的笔记本电脑(或 Slack 中的 Claude 会话)出发,要经过多层代码审查。

一次变更的生命周期

一次典型的 Claude Code 变更,从作者的机器到用户

图 A大部分路径是自动化的;人的注意力投向风险所在之处

逐层解析

是多层,而非一道关卡

六层检查,其下还有回滚

图 B每一层都能拦住一些问题,也会漏掉另一些。很少有能通过所有层的,剩下的由回滚处理。

1. 在任何人查看之前先验证工作

现在大多数审查发生在 PR 存在之前。我们给 Claude 一些它可以运行的东西——比如测试、构建,或一张用来与设计稿对比的截图——并要求它展示输出,而不是说已经完成。然后由一个子智能体带着全新的上下文审查 diff。这两个习惯都来自我们的最佳实践指南。

在开 PR 之前,我们中的许多人还会运行几个内部技能:一个查找 bug,一个简化 diff,一个端到端检查行为。UI 变更还会对照我们的设计指南进行检查。

Claude Code 的创造者 Boris Cherny 曾称,给 Claude 一种验证其工作的方式大概是最重要的事情以获得好的结果。他估计这能让最终结果提升两到三倍,并写道:“验证在每个领域看起来都不一样。它可能简单到只是运行一条 bash 命令,或者运行一个测试套件,或者在浏览器或手机模拟器中测试应用。”

当自动审查器反复标记同一个问题时,我们从源头修复它:在技能中、用一条 lint 规则,或者通过更新指导性文件,例如:

  • CLAUDE.md告诉 Claude 这个项目是什么:技术栈、约定、目标和非目标。每次审查都会加载它,所以在大型代码库中我们保持它简短,并从那里指向其他文档。审查器还会标记违反其中约定的代码,以及让其中某一行过时的改动。
  • REVIEW.md由代码所有者撰写,告诉审查器在他们负责的那部分代码中什么才是重要的。我们会单独检查审查器是否真的遵循了它。好的版本都很简短。它们说明在那个仓库中失败是什么样子(对文档来说是“它在撒谎”,对配置来说是“它路由错了”),限制吹毛求疵的数量,列出要跳过的路径,并要求提供文件和行号作为证据,而不是根据名字猜测。它们还要求摘要以计数开头,并在没有需要修复的内容时直说。

2. 对每个拉取请求进行 AI 审查

每个拉取请求在有人查看之前都会先得到一次 Claude 审查。它会对照整个仓库和 PR 声明的意图来阅读 diff,并询问:这个改动解决了它的问题吗,契合系统吗,还是破坏了别的东西?

在 bug 修复上,它会寻找根本原因。公开版本,Claude Code 的代码审查现已在 Team 和 Enterprise 计划上全面可用,并且/code-review在本地运行一个更轻量的版本。

当我们推出研究预览版时,获得实质性审查评论的内部 PR 占比从 16% 上升到 54%。大多数超过 1,000 行的 PR 都能发现问题,而大多数小型 PR 则返回无问题。管理员将每个仓库设置为 Quick、Standard 或 Deep。在漏掉一个 bug 的代价高于额外审查时间的地方,Deep 是值得的。

在人类审查之前,发现的问题会由其他智能体进行验证。多个智能体同时查找 bug。然后由另一个小组对每条发现进行投票,只有足够多的人同意它是真实问题,才会被发布。仅涉及风格、诊断或清理的发现会被丢弃。我们在保留这些过滤器之前,先在自己的 PR 上做了 A/B 测试。剩下的内容会以按严重程度排序的行内评论形式出现。每条都标记为 Important、Nit 或 Pre-existing,这样 PR 未引入的 bug 就不会看起来像是作者的错。

每条发现都必须得到回应。你要么修复它,要么附上理由解决该讨论串。通常智能体会在一夜之间接手修复,所以作者回来时会看到一个已经经过另一轮处理的 PR。

3. 可以阻止合并的安全审查

安全也有自己的自动化审查。只要有来自 bug 审查或安全审查的任何发现仍未关闭,PR 就不能合并。你要么修复它,要么附上简短理由关闭讨论串,这就是清除误报的方式。

如果后续提交修复了该问题,这条发现会自动清除。你可以用开源安全审查 GitHub Action 或/security-review命令来设置同样的机制。设置方法见Claude Code 中的自动化安全审查。

4. 可以阻止合并的自动化检查

常规的那些:lint、类型检查、各平台上的测试,以及构建。(我们写过如何为这种规模扩展 CI这里。)一个典型的 CLI pull request 在任何人查看之前可能会运行几十项检查。PR 模板会询问改动是如何测试的,以及如何撤销。Claude 可以填写这些内容,但提出改动请求的人要签字确认。

5. 人工批准,按风险分级

文档修复和自动更新器改动承载的风险不同,所以它们会得到不同的审查。

风险示例人工批准
低文档、文案、仅内部使用的功能、位于 flag 之后的增量改动一旦自动化审查和安全检查通过,无需批准
中普通产品改动,以及对缓存、自动更新器或系统提示等敏感区域的改动一人
高风险最高的代码,外加一小部分始终需要两人的路径两人

分级由改动决定,而不是作者。一个分类器读取变更并选择其风险等级。第二个分类器会询问这个特定变更是否需要另一个人,即使其等级表明不需要。某些路径始终需要一个人,所有者列为敏感的任何内容以及任何 CI 工作流文件。

只有受信任的作者才能走更轻量的路径。只有仓库受信任列表上的作者才能走更轻量的路径,主要是拥有该仓库的团队。其他所有人都需要第二个人批准。智能体的批准也永远不算作人的批准。自动化审查可以放行低风险变更,但它不能填补需要人类的空缺。

发起该变更的人要对其负责。在智能体编写的 PR 上,指导智能体的人算作所需审查者之一,就如同他们自己输入了代码一样。

一次批准只覆盖一个特定提交。如果系统无法担保的新提交落地,比如智能体的推送或有人手动解决的合并,批准就必须重新给出。

6. 合并队列、功能标志和渐进式发布

合并队列会在应用变更的情况下针对 main 重新运行每一项检查,因此两个分别通过的 PR 不会合并成一个损坏的 main。新行为通常先通过功能标志发布给 Anthropic 员工,而我们的大量使用往往会在用户之前发现回归。公开发布先发到latest频道。stable频道随后跟进。

7. 快速检测,快速回滚

有些错误仍会漏过去,所以最后一层是关于快速捕获它们。功能标志无需部署即可关闭。Claude Code 有每周轮值的 on-call,事故复盘会问哪一层本应捕获该问题。

注意到糟糕的合并仍然取决于作者和 on-call,以及回滚和事故仪表盘。我们还没有在那些数字变动时收紧审批的熔断机制;这里的工程师希望在更多代码转向零人类合并之前拥有它们。

一位同事说放慢速度确实会降低错误率,但主要是因为你在做更少的所有事情。我们更愿意在发现和修复问题上变得更快。

我们如何让审查者变得更好

我们的代码审查流程在一个循环中不断改进。

任何事故现在都是一项评估。团队收集了过去引发事故的 PR,连同它们的修复前滚和回滚,并让熟悉那段代码的工程师确认每个 bug 及其所在位置。审查器每天针对这个集合运行。

我们的事故就是评估集

由过去的事故和回滚构建,每天运行

图 C评估集随每次事故增长,审查器的每一次改动都会针对全部评估集打分。

我们通过观察人们如何处理评论来衡量噪声。如果你不介意标记大量非 bug,就很容易抓到更多 bug,所以我们需要对此加以检查。我们按 PR 上的人如何回应来对审查器留下的每条评论分类:他们修复了它、说它是错的、说该行为是有意为之,或者认为它无关紧要。我们还会在已知干净的 PR 上运行审查器,统计它报告了多少次本会引导人做出有害改动的问题。我们写明的优先级是:先抓真正的 bug,其次减少审查轮次。

当作者是智能体时,噪声的代价更高。人可以几秒钟就打发掉一个吹毛求疵的意见。智能体会去修复它,这意味着又一次提交和又一轮审查。

审查必须收敛。智能体作者和智能体审查器可以让彼此无限期地忙碌下去。一个 CLI PR 在一夜之间经历了五十多轮审查和修复,直到其作者叫停。

当我们对审查器反复标记的内容运行 git blame 时,约 59% 是自审查器第一轮以来就一直存在的代码。审查器很少发现新 bug,它每轮只是多发现几个旧 bug。

我们自己让它变得更糟。我们已经习惯默认运行多轮,因为每一轮都能抓到新东西。这在有风险的改动上可能值得,但不是每个 PR 都值得。团队现在将通过与召回率并列的通过所需轮次来跟踪,并改动了重新审查:

  • 轮次上限。三轮之后,审查器会请作者退后一步,重新考虑方案。
  • 仅 diff 重新审查在最初几轮之后。
  • 标记 bug 类别,让作者修复每一个实例,而不是一行。
  • 不重复已发布的发现。

审查智能体还会每晚对现有代码运行,帮助我们在旧 bug 出现在别处之前抓到它们。

收敛的审查

内部拉取请求,在收敛性变更前后的对比

图 D收敛性变更后,每个内部拉取请求的评审轮次更少

人类在哪些环节保持参与

把一位资深工程师从深度工作中拉出来去抓一个常规错误,代价远高于一次自动化评审。在 Anthropic,我们的工作已经从阅读每一个 diff 转变为做那些机器不擅长的决策:

  • 我们到底该不该做这件事?方向、品味和范围。有一个团队宣称其评审瓶颈已经解决,但仍然对来自团队外部的变更保留人工评审,以便有人问一句“我们到底该不该做这件事?”
  • 有风险的代码例如计费、自动更新器和 CI 工作流。
  • 架构即会树立一个被他人效仿的模式。
  • 阅读证据。测试输出、运行了哪条命令及其返回结果、一张截图、评审者的发现。如果你说不出这些检查会漏掉什么,那就要求一次真正的评审,而不是快速批准。
  • 对结果负责。每一次上线的变更都有人负责,无论代码是谁敲的。

由人做决定的地方

五项决策留给人;其余环节都跑检查

图 E人们从阅读每一个 diff 转变为做那些机器不擅长的决策

在团队内共享上下文

代码评审的第二项职责一直是传播对代码库的理解。阅读队友的 diff 是你了解发生了什么变化、各部分如何拼合的方式。如果大部分阅读都由机器完成,那人们还怎么学习代码?

我们没有为此设立正式流程。团队里没有人脑子里装着 Claude Code 的完整心智模型,我们也认为没人需要这样。当有人想理解代码库的某一部分时,他们会请 Claude 针对那一小块写一份讲解。这只需几分钟,而且反映的是代码当下的样子。

Diagram titled "If machines do most of the reading, how does anyone learn the code?" On the left, a grid stands for the codebase, with a few squares highlighted as managed settings, the part you want to understand; nobody keeps a complete mental model of it in their head. An arrow labeled "Ask Claude about exactly that slice", with the example question "How does Claude Code retrieve managed settings?", leads to two outcomes: Claude writes an explainer, which takes minutes and reflects the code as it is today; and, for some, Claude builds a small artifact that visualizes that part of the code and teaches it the way that person likes to learn. Along the bottom: reading trivial diffs line by line, a one-line copy change teaches you almost nothing; an explainer for the module you are about to change teaches you a lot.

我们中有些人更进一步,让 Claude 构建一个小工件,把代码的那部分可视化出来,并按那个人喜欢的学习方式来讲解。

如果目标是把人的注意力花在刀刃上,这比逐行阅读琐碎的 diff 要好得多。一行文案改动几乎教不了你什么。而针对你即将修改的模块写一份说明,则能让你学到很多。我们默认在开放的 Slack 频道里工作的文化也有帮助,因为你能在别人构建的同时看到他们在做什么。

“你合入,你负责”

如果你合入了一个小型或中型改动,而之后仍然出了问题,团队惯例通常是由你来修复。有几点可以防止这件事变糟:

  • 改动很小,因此易于审查、调试和回滚。
  • 一切皆可回滚。功能开关、回滚和分阶段发布能在几分钟内撤销大多数错误。
  • 影响范围决定门槛。代价高的代码会得到更多人和更多检查。
  • 回归问题会很快浮现,通过遥测和内部使用。
  • 这不是追责。负责修复意味着你掌握最多的上下文;事故复盘关注的是系统。

责任也是共享的。团队中很多人工作在开放频道里,任何人都能看到别人在构建什么、接手一个讨论串,或者让 Claude 去填补某个缺口,反馈也来自并不拥有这段代码的人。这种开放性是质量在没有逐行把关的情况下依然保持高水平的重要原因。

当我们改变方向时

智能体让启动事情变得廉价,所以有些工作不该合入。通常频道里会有人问为什么、指出早先的决定,或建议一条更简单的路径,然后作者关闭或重塑这个 PR。

既然代码写得很快,关闭 PR 就不会让人觉得是损失。诀窍是在讨论串里尽早提出不同的方向,而不是作为对已完成 PR 的阻塞性评论。

你如何在团队中采用这套流程

与团队一起实践这套流程所需的大部分组件今天都已具备。

在提交拉取请求之前

  • 给你的智能体一些它能运行的检查,列在一个简短的CLAUDE.md中,再加上运行格式化和 linter 的钩子。
  • 在本地运行/code-review和/security-review,或者做一次全新上下文的子智能体审查。
  • 添加一个REVIEW.md,并把重复出现的审查评论移进去。

在每个拉取请求上

  • 要求 CI,并在 PR 模板中加入“测试”、“风险”和“如何回滚”。
  • 在一个仓库上添加 AI 审查者(Claude Code 的 Code Review,或 Claude Code GitHub Action),调整REVIEW.md几周,然后再扩大范围。
  • 加入自动化安全审查,并要求每个审查线程在合并前都已解决。
  • 让审查力度与风险匹配:常规变更快速过一遍,漏掉 bug 代价高昂的地方则深入审查(Code Review 中的 Quick、Standard 或 Deep)。
  • 限制审查轮次;前几轮之后,只重新审查新的 diff。

审批

  • 写下风险等级,依据变更而非作者来设定;从严开始,凭证据逐步放宽。
  • 对你信任的作者保留更轻量的审批路径。
  • 使用 CODEOWNERS 和分支保护,让 auth、billing、CI 与发布工作流以及安装程序始终需要人工介入。
  • 绝不让机器人的审批替代必需的人工审批;有新提交时撤销审批。

合并及之后

  • 使用合并队列、功能开关、先内部使用、分阶段发布和一键回滚。
  • 把事故和回滚转化为评测集,并在审查器的提示词、工具或模型变化时重新评分。
  • 衡量人工时间,以回滚率和事故率作为护栏。

这一切将走向何方

Code Review 的公告表示批准拉取请求仍是人的决定;该产品发布的是发现,而非批准。我们自己的仓库走得更远,让所有者决定哪些低风险变更仅凭自动化检查即可合并,而这条界线还在不断移动。

当审查漏掉某些东西时,我们会追问模型当时眼前缺了什么,并把修复放进一个CLAUDE.md、一项技能或一项新检查中。目标没有改变:每次变更消耗更少的人工时间,且回滚不多于以往。

如果你的团队让智能体发起拉取请求,我们想听听你自动化了哪些检查、哪些仍需要人工,以及你认为我们哪里做错了。

感谢 Emma Webb、Michael Segner 和 Amie Rotherham 的审阅。