Claude Code 团队如何进行代码审查
在智能体产出代码的体量下,你如何进行代码审查?在 Claude Code 团队,一旦我们实现了高覆盖率和深度审查,我们就开始优化:在不增加回滚或事故的前提下,将人工审查时间降至最低。
我们之前说过,Claude 编写了 Claude Code 自身约 90% 的代码。那么,我们如何在如此大的产出量下进行代码审查?现在还有人读代码吗?如果出了问题,谁来发现?
本文介绍了 Claude Code 团队的代码审查流程及其演变。从 6 月到 9 月,Claude Code 代码库的合并请求数量增加了一倍多,而需要回滚的比例下降了 40% 以上。
我们没有取消审查。但我们把大部分阅读工作交给了机器,为人类的时间和注意力进行了优化,并投入大量精力让错误的修正变得快速而容易。
重新思考代码审查的目的
当智能体编写代码时,变更数量会迅速增加。在 Anthropic 的 monorepo 中,每天落地的变更从今年 5 月的约 2,500 个增加到 9 月的 30,000 多个。
Claude Code 位于其独立的仓库中,与 monorepo 分开,但它也面临着很大的压力。当你以如此快的速度发布时,手动的、逐行的代码审查如果不是不可能的话,也是极其困难的。
代码审查有两项工作:
- 在发布前发现并修复问题
- 在团队中建立共享的上下文和代码库理解
本文主要关注发现和修复问题,但我们将在接近尾声时回到第二项工作。
我们认为,“有人类读过这段代码吗?”是一个不精确的代理指标,真正的问题是:“这次变更可能会出什么问题,哪种检查——自动化的还是人工的——才是发现它的正确方式?”
提出这个问题也改变了何时进行审查。以前,审查是路途终点的一道关卡:写好代码,提交 PR,等待同事。
现在,审查是持续运行的,更像是你写作时的拼写检查。大多数变更都已经被 Claude 至少审查和修复过一次,然后才会请人类查看。这能发现大多数常规错误,并将团队的注意力留给更高层次的问题和审查。
稀缺资源:人类的时间和注意力
我们希望将人类的时间和注意力保留给只有人类才能解决的问题,并确保自动检查能捕捉到其余问题。以我们目前的变更速度,人类仍有大量工作要做,我们的团队几乎每天都会遇到新的、前所未有的挑战。
人类时间涵盖了审查成本的两个方面:人们花在审查上的时间,以及他们花在修复糟糕代码所造成破坏上的时间。如果节省的时间只是转移到了第二个方面,那么削减第一个方面并没有帮助。
因此,目标是 Claude Code 各仓库(CLI、桌面和 Web 应用以及移动应用)中的大多数拉取请求在合并时最多只有一名人类参与(可以是作者本人),且回滚或事故没有增加。为了衡量人类时间,我们跟踪每个 PR 的人工批准数(中位数和第 95 百分位数)、人工评论,以及 PR 等待人工处理的时间。回滚率和事故率是护栏:如果它们上升,说明我们并没有节省人类时间,只是把它转移了。
一次变更的生命周期
一次典型的变更,从工程师的笔记本电脑(或 Slack 中的 Claude 会话)出发,要经过多层代码审查。
一次变更的生命周期
一次典型的 Claude Code 变更,从作者的机器到用户
拉取请求的检查,自上而下。在拉取请求之前,工作先经过验证。每个拉取请求上:必需的 CI、Claude 代码审查和安全审查,全部自动化,然后是基于风险的审批,由人来决定。接着是合并队列,合并之后先在内部使用功能开关,逐步发布,最后一步是观察、向前修复或回滚。
逐层
分层,而非一道关卡
六层检查,其下是回滚
六层自上而下堆叠:验证、必需的 CI、AI 审查、安全审查、人工审批,以及合并队列加功能开关。每一层在不同位置都有漏洞。从漏洞中漏掉的问题会被后面的某一层接住;如果一个问题穿过了每一层的漏洞,就会落到最底部的快速回滚。
1. 在其他人查看之前,工作先经过验证
现在大部分审查发生在 PR 存在之前。我们给 Claude 一些它可以运行的东西——比如测试、构建,或一张与设计对比的截图——并要求它展示输出,而不是说它完成了。然后一个子智能体带着全新的上下文审查差异。这两个习惯都来自我们的最佳实践指南。
在开 PR 之前,我们中的许多人还会运行几个内部技能:一个查找 bug,一个简化差异,一个端到端检查行为。UI 变更还会对照我们的设计规范进行检查。
创建了 Claude Code 的 Boris Cherny 曾称,给 Claude 一种验证其工作的方式是获得好结果的最重要的事情。他估计这能让最终结果好上两到三倍,并写道:“验证在每个领域看起来都不一样。它可能简单到只是运行一个 bash 命令,或者运行一个测试套件,或者在浏览器或手机模拟器中测试应用。”
当自动审查者不断标记同一件事时,我们从源头修复它:在技能中、用 lint 规则,或通过更新指令文件,例如:
CLAUDE.md告诉 Claude 这个项目是什么:技术栈、约定、目标和非目标。每次审查都会加载它,所以在大型代码库中我们保持它简短,并从中指向其他文档。审查者还会标记违反其中约定的代码,以及使其某一行过时的更改。REVIEW.md,由代码所有者编写,告诉审查者他们那部分代码中什么最重要。我们会单独检查审查者是否真的遵循它。好的那些都很简短。它们说明在该仓库中失败是什么样子(对文档来说是“它在撒谎”,对配置来说是“它路由错了”),限制吹毛求疵的数量,列出要跳过的路径,并要求提供文件和行号作为证据,而不是根据名字猜测。它们还要求以计数开头的摘要,并在没有需要修复的内容时明确说明。
2. 每个拉请求上的 AI 审查
每个拉取请求在人查看之前都会得到一次 Claude 审查。它对照整个仓库和 PR 声明的意图来阅读差异,并提出问题:这个更改是否解决了它的问题、是否契合系统、或是否破坏了其他东西?
在 bug 修复中,它会寻找根本原因。公开版本 Code Review for Claude Code 现已在 Team 和 Enterprise 计划中正式可用,而 /code-review 在本地运行一个更轻量的版本。
当我们 发布研究预览版 时,获得实质性审查评论的内部 PR 比例从 16% 提高到了 54%。大多数超过 1,000 行的 PR 都会得到发现,而大多数小型 PR 则会干净通过。管理员可以将每个仓库设置为快速、标准或深度模式。当一个遗漏的 bug 造成的代价超过额外的审查时间时,深度模式是值得的。
发现会在人类审查之前由其他智能体进行验证。多个智能体同时寻找 bug。然后由另一组智能体对每个发现进行投票,只有当足够多的智能体同意它是真实存在的时候,才会发布。仅涉及样式、诊断或清理的发现会被丢弃。我们在自己的 PR 上对这些过滤器进行了 A/B 测试,然后才保留它们。剩下的发现会以按严重程度排序的内联评论形式出现。每条评论都标记为重要、吹毛求疵或已存在,这样 PR 未引入的 bug 就不会看起来像是作者的错。
每个发现都必须得到回应。你要么修复它,要么给出理由解决该讨论串。通常智能体会在夜间接手修复,因此作者回来时,PR 已经又经过了一轮审查。
3. 可以阻止合并的安全审查
安全也有自己的自动化审查。只要 bug 审查或安全审查中还有任何发现未解决,PR 就无法合并。你要么修复它,要么给出简短理由关闭讨论串,这就是你清除误报的方式。
如果后续提交修复了该问题,发现会自动清除。你可以使用开源安全审查 GitHub Action 或 /security-review 命令来设置相同的功能。设置方法在 Claude Code 中的自动化安全审查 中有介绍。
4. 阻止合并的自动化检查
常规项目:代码检查、类型检查、每个平台的测试以及构建。(我们曾在此处撰写过关于扩展 CI 以应对这一规模的文章。)一个典型的 CLI 拉取请求在任何人查看之前可能会运行数十项检查。PR 模板会询问更改是如何测试的以及如何撤销。Claude 可以填写这些内容,但要求进行更改的人需要签字确认。
5. 按风险分级的人工批准
文档修复和自动更新器更改所携带的风险不同,因此它们会得到不同的审查。
| 风险 | 示例 | 人工批准 |
|---|---|---|
| 低 | 文档、文案、仅限内部的功能、标志位后的增量更改 | 一旦自动化审查和安全检查通过,无需批准 |
| 中 | 普通产品更改,以及对敏感区域(如缓存、自动更新器或系统提示)的更改 | 一人 |
| 高 | 风险最高的代码,以及一小部分始终需要两人的路径 | 两人 |
是更改决定了级别,而不是作者。一个分类器会读取更改并选择其风险级别。第二个分类器会询问这次特定的更改是否需要另一个人,即使其级别显示不需要。某些路径始终需要人工,包括所有者列出的任何敏感内容以及任何 CI 工作流文件。
只有受信任的作者才能走更轻量的流程。只有在仓库受信任列表上的作者才能走更轻量的流程,这主要是拥有该仓库的团队。其他所有人都需要第二个人批准。智能体的批准也永远不算作人工批准。自动化审查可以放行低风险更改,但它无法填补需要人工的名额。
无论谁促使了这次改动,都要为此负责。在由智能体编写的 PR 中,指挥智能体的人算作必需的评审者之一,就像他们亲自编写了代码一样。
一次批准只针对一个特定的提交。如果出现了系统无法担保的新提交,比如智能体的推送,或者某人手动解决的合并,就必须重新批准。
6. 合并队列、功能开关与渐进式发布
合并队列会针对 main 分支重新运行每一项检查并应用该改动,因此两个单独通过的 PR 不会组合成一个损坏的 main。新功能通常先通过功能开关向 Anthropic 员工发布,而我们对它的大量使用往往能在用户之前暴露出回归问题。公开发布会先发布到 latest 渠道,stable 渠道随后跟进。
7. 快速检测,快速回滚
有些错误仍然会溜过去,所以最后一层是为了快速发现它们。功能开关无需部署即可关闭。Claude Code 有每周轮值的值班安排,事故复盘会追问本应由哪一层来发现问题。
发现糟糕的合并仍然依赖作者和值班人员,以及回滚和事故仪表盘。我们目前还没有能在这些数字变动时收紧审批的熔断机制;这里的工程师希望在更多代码转向零人工合并之前先有这些机制。
一位同事说,放慢速度确实会降低错误率,但主要是因为你做所有事情的量都变少了。我们宁愿在发现问题和修复问题上变得更快。
我们如何改进评审者
我们的代码评审流程在一个循环中不断改进。
任何事故现在都是一次评测。团队收集了过去引发事故的 PR,连同它们的前向修复和回滚,并让熟悉那段代码的工程师确认每个 bug 及其所在位置。评审者每天都会针对该数据集运行。
我们的事故就是评测
由过去的事故和回滚构建,每天运行
事故或回滚会变成一个经过专家核验的案例,并被加入评测集。对评审者的改动会进入每日评测运行,针对每个案例打分;如果分数上升就保留该改动,否则就丢弃,然后循环再次从下一个改动开始。
我们通过观察人们对评论的反应来衡量噪音。如果你不介意标记大量非问题,就很容易抓到更多 bug,所以我们需要对此加以约束。我们根据 PR 上相关人的回应方式对评审者留下的每条评论进行分类:他们修复了、说它是错的、说该行为是有意为之,或者认为无关紧要。我们还会在已知干净的 PR 上运行评审者,并统计它报告出可能导致他人做出有害改动的情况的频率。我们写下来的优先级是:首先抓出真正的 bug,其次减少评审轮次。
当作者是智能体时,噪音的代价更高。一个人可以在几秒钟内忽略一个吹毛求疵的问题。而智能体则会去修复它,这意味着又多了一个提交和又一轮评审。
评审必须收敛。一个智能体作者和一个智能体评审者可以无限期地互相折腾。有一个 CLI 的 PR 在一夜之间经历了五十多轮评审和修复,直到它的作者叫停了它。
当我们对审查者反复标记的内容运行 git blame 时,发现其中约 59% 是自审查者第一轮审查以来就存在的代码。审查者很少发现新 bug,而是每轮多找出几个旧 bug。
我们自己让情况变得更糟。我们已经习惯默认运行多轮,因为每一轮都能发现新问题。对于高风险的改动这可能值得,但对每个 PR 都这样做就不必了。团队现在将通过所需轮数与召回率一起跟踪,并改变了复审方式:
- 轮数上限。三轮之后,审查者会请作者退一步重新考虑方案。
- 仅针对差异的复审,在前几轮之后采用。
- 标记 bug 的类别,这样作者能修复所有实例,而不是只改一行。
- 不重复已经提出过的问题。
审查代理还会在夜间对现有代码运行,帮助我们在旧 bug 出现在别处之前就抓住它们。
收敛的审查
内部拉取请求,收敛改动前后
收敛工作前后对比:经过一轮审查和修复就通过的拉取请求从 65% 上升到 78%;需要五轮或更多轮的拉取请求从 8.9% 下降到 1.5%;平均通过所需审查轮数从 2.08 降到 1.32。
人类仍在环中的环节
把一位资深工程师从深度工作中拉出来去抓一个常规错误,成本远高于一次自动审查。团队成员仍负责:
- 决定是否要做。方向、品味和范围。有一个团队宣布其审查瓶颈已解决,但对来自团队外部的改动仍保留人工审查,以便有人会问"我们真的该做这个吗?"
- 审查高风险代码,例如计费、自动更新器和 CI 工作流。
- 审查架构,即设定他人会照搬的模式。
- 阅读证据。测试输出、运行的命令及其返回结果、截图、审查者的发现。如果你说不出这些检查会漏掉什么,就要求真正的审查,而不是快速批准。
- 为结果负责。每一个上线的改动都有一个人负责,无论代码是谁敲的。
在团队间共享上下文
代码审查的第二项工作一直是传播对代码库的理解。阅读队友的差异是了解发生了什么变化以及各部分如何衔接的方式。如果大部分阅读都由机器完成,那人们怎么学习代码?
我们没有为此制定正式流程。团队中没有人脑子里装着 Claude Code 的完整模型,我们认为也不需要。当有人想理解代码库的某一部分时,他们会请 Claude 恰好为那一部分写一份讲解。只需几分钟,而且反映的是代码今天的样貌。
我们中有些人更进一步,让 Claude 构建一个小工具来可视化那部分代码,并以那个人喜欢的学习方式来讲解它。
如果目标是善用人类的注意力,这胜过逐行阅读琐碎的差异。一行文案改动几乎教不了你任何东西。而为你即将修改的模块写的讲解则能让你学到很多。我们默认在公开的 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)。
- 限制审查轮数;前几轮之后,只重新审查新增的差异。
审批
- 写下风险等级,等级由变更而非作者决定;开始时从严,再根据证据放宽。
- 为你信任的作者保留更轻量的审批路径。
- 使用 CODEOWNERS 和分支保护,使认证、计费、CI 和发布工作流以及安装程序始终需要人工介入。
- 绝不能让机器人的审批取代必需的人工审批;有新提交时撤销已有审批。
合并及之后
- 使用合并队列、功能开关、先内部使用、分阶段发布和一步回滚。
- 把事故和回滚转化为一个评测集,并在审查者的提示词、工具或模型发生变化时重新评分。
- 衡量人力时间,以回滚率和事故率作为护栏。
这一切将走向何方
Code Review 的发布公告说,批准拉取请求仍然是人类的决定;产品发布的是发现,而不是批准。我们自己的仓库走得更远,让所有者决定哪些低风险变更可以仅凭自动检查合并,而这条界线仍在不断移动。
当审查漏掉某个问题时,我们会问模型当时面前缺少什么,并把修复放进一个 CLAUDE.md、一个技能或一个新的检查中。目标没有变:每次变更耗费的人力时间更少,回滚次数不比以前更多。
如果您的团队允许智能体开启拉取请求,我们希望了解您已经自动化了哪些检查、哪些仍需人工完成,以及您认为我们哪里有误。
感谢 Emma Webb、Michael Segner 和 Amie Rotherham 的审阅。