我们之前提到过,Claude Code 大约 90% 的代码都是由 Claude 编写的。那么,在这种规模下我们该如何做代码审查(Code Review)?到底还有谁在读代码?如果代码出了问题怎么办?

这篇文章会介绍我们 Claude Code 团队的代码审查流程,以及我们是如何改进它的。在 6 月到 9 月之间,合并到 Claude Code 代码库的 PR(Pull Request)数量翻了一倍多,而不得不回滚(revert)的 PR 比例却下降了 40% 以上。

我们并没有停止代码审查。相反,我们把绝大部分阅读代码的工作自动化了,并围绕节省人力和时间对流程做了调优。同时,我们也投入了大量精力,让回滚错误变得更快、更简单。

重新思考代码审查的目标

由 Agent 来写代码后,代码改动的数量会急剧增加。在我们的单体仓库(monorepo)中,每天合并的改动数量从今年 5 月的大约 2,500 个,飙升到了 9 月的超过 30,000 个。

虽然 Claude Code 不在这个单体仓库里,但它同样面临很大的压力。在推进速度这么快的情况下,想要靠人工逐行做代码审查会变得越来越难,甚至根本不可能。

代码审查有两个目的:

  1. 在上线前发现并修复 bug
  2. 建立团队对代码库的共同理解

本文主要聚焦第一个目标,并会在文末讨论第二个目标。

我们认为,“有没有人读过这段代码?”其实是一个不够准确的衡量标准;真正的问题应该是:“这次改动可能会出什么错?哪种检查——自动检查还是人工审查——最适合抓住这个问题?”

这个问题也改变了代码审查的时机。传统上,代码审查是一种事后检查:代码写完、提交 PR,然后等着另一个人来看。而我们把代码审查融入到了工作流里,更像是写作时持续运行的拼写检查。

在请人类介入查看之前,绝大多数改动都已经由 Claude 审查并修正过至少一次了。通过这种方式,我们能抓出大部分常规 bug,把人类的注意力留给更高层面的考量。

人类的时间与注意力

我们希望把人类的时间和注意力留给只有人类能处理的事情,同时确保有足够的自动化机制来覆盖其余部分。即便按我们目前的推进速度,仍然有大量工作需要人类去做,团队几乎每天都在遇到全新的挑战。

正因为如此,人类的时间是一项双面成本。我们不仅要计算大家花在代码审查上的时间,还要计算修复由问题代码引入的 bug 所花的时间。如果只减少了前者而没有控制住后者,那我们并没有真正节省人类的时间。

因此,我们的目标是:在 Claude Code 的各个代码仓库(包括 CLI、桌面端与网页端应用,以及移动端应用)中,绝大多数 PR 最多只需要一个人介入(这个人可以是作者本人)就能合并,且回滚率或故障率不会上升。

我们通过这几个指标来追踪人力耗时:每个 PR 的人工批准数中位数(以及 P95 分位数)、人工评论数量,以及 PR 等待人工处理的时长。同时,我们把回滚率和故障率作为护栏指标(guardrail):如果这两个指标上升了,说明我们并没有减少人力时间,只是把时间消耗转移到了别处。

一次代码改动会经历什么?

一次典型的代码改动,无论来自工程师的笔记本电脑,还是来自 Slack 里的 Claude 会话,都会经过多层审查。

一次代码改动的生命周期

上图是一次典型的 Claude Code 改动从作者本地环境到生产环境的全过程。这个流程的大部分环节都是自动化的,人类的注意力则集中在高风险部分。

我们设置了六层检查,并配有一套回滚机制来处理任何漏网的问题。

多层防线,而非单一关卡

每一层都会抓出一些 bug,同时也会漏掉另一些。极少有 bug 能穿透所有防线;就算真有漏网之鱼,我们也有回滚机制兜底。

1. 提交 PR 前的验证

我们大部分的审查工作其实在创建 PR 之前就已经完成了。我们会让 Claude 执行一些可运行的验证,比如跑测试、执行构建,或者把界面截图与设计稿做对比。我们不会只听它说“任务完成了”,而是要求它把运行结果展示出来。接着,一个拥有全新上下文的子 Agent(subagent)会对代码改动(diff)进行审查。这两项策略都收录在我们的最佳实践指南中。

在发起 PR 之前,我们很多人还会运行几个内部 Skill——排查 bug、精简 diff、端到端验证,以及对照设计规范检查 UI 改动。

正如 Claude Code 的创建者 Boris Cherny 所说,为 Claude 配备一套验证流程,可能是获得更高质量产出最关键的因素。他估计,验证流程能让 Claude 的产出质量提升两到三倍。

不同领域的验证流程可能会有很大差异:有时只是跑一条 bash 命令那么简单,有时是运行一套测试套件,有时则是在浏览器或手机模拟器里测试应用。

如果自动审查工具总是反复标记同一类问题,我们会从源头解决它——比如直接修改对应的 Skill、添加一条 lint 规则,或者更新以下文档文件:

  • CLAUDE.md —— 用于向 Claude 提供项目上下文的项目说明文件(包含技术栈、代码规范、目标与非目标)。Claude Code 在每次审查时都会引入这个文件,所以针对大型代码库,我们会让它保持简短,并在里面引用其余文档。当代码违反了该文件中的规范,或者某次改动导致文件中的内容过时,审查工具就会标记出来。
  • REVIEW.md —— 代码负责人(code owners)用来告诉审查工具“自己负责的模块里哪些事情最重要”的文件。我们还会单独验证审查工具是否真的遵守了这些指南。一份好的 REVIEW.md 通常简明扼要,重点关注:
    • 定义什么是“项目级错误”(例如:文档不能说谎;配置不能错误路由)
    • 限制吹毛求疵的细节挑刺(nitpicking)
    • 定义需要忽略的路径
    • 要求给出具体的文件名和行号引用,而不是猜测文件名
    • 要求提供顶层总结,包含问题数量,或在没问题时明确写出“无问题(no issues)”

2. 每个 PR 上的 AI 审查

每个 PR 在进入人工审查之前,都会先接受一次 Claude 审查。

它会结合整个代码库以及 PR 作者声明的改动意图来分析 diff,并检查:

  • 这次改动确实解决了对应的问题
  • 这次改动能够融入更大的系统架构
  • 这次改动不会破坏代码库的其他部分

在审查 bug 修复时,机器人还会尝试找出根本原因(root cause)。

我们的公开产品 Code Review for Claude Code 现已向所有 Team 和 Enterprise 客户开放。同时也有一个供本地使用的轻量版本——/code-review。

自发布研究预览版以来,我们内部收到实质性审查意见的 PR 比例从 16% 提升到了 54%。对于超过 1,000 行的大型 PR,Code Review 能在绝大多数情况下检测出问题;而对于大多数小型 PR,它则会返回干净的通过结果。管理员可以为仓库配置快速(Quick)、标准(Standard)或深度(Deep)模式。当漏掉一个 bug 的代价高于代码审查所花的时间时,深度模式非常有用。

在交给人工审查之前,发现的问题会先由多个其他 Agent 进行交叉核验:

  • 多个 Agent 并行运行以搜寻 bug
  • 独立的投票机制会判断某个问题是真实有效的还是误报(false positive),只有获得足够票数的问题才会被确认
  • 仅涉及代码风格、诊断信息或代码清理的意见会被过滤掉(在推广这些过滤器之前,我们在自己的 PR 上做过 A/B 测试)

筛选后留下的问题会以行内评论(inline comments)的形式展示,按严重程度排序并标记为:

  • Important(重要问题)
  • Nit(细节问题)
  • Pre-existing(既有问题——这样我们就不会因为并非由该 PR 引入的 bug 而不公平地苛责作者)

所有被指出的问题都必须得到处理——要么修复,要么附上理由把讨论串标记为已解决(resolved)。很多时候,Agent 会在夜间自动把问题修好,第二天作者回来时,看到的已经是通过了新一轮审查的 PR。

3. 可阻断合并的安全审查

我们还会运行自动化的安全审查。只要还有未解决的问题(无论来自代码审查还是安全审查),PR 就无法合并。作者可以选择修复问题,或者附上简短理由关闭该讨论串(这也是我们处理误报的方式)。

如果后续提交(commit)解决了该问题,对应的审查项会自动关闭。你可以使用开源的安全审查 GitHub Action 或 /security-review 命令搭建同样的机制。具体配置方法可以参考 Claude Code 中的自动化安全审查指南。

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

标准检查包括:lint、类型检查、全平台测试以及构建。一个典型的 CLI PR 在任何人看到它之前,可能已经跑了几十项检查。PR 模板里包含了关于“如何测试该改动”以及“如何回滚”的问题。Claude 可以帮你填写这些问题,但作者有责任对内容进行复核。

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

有些改动的风险比其他改动更高(比如修复文档 vs. 修改自动更新器),因此它们需要不同级别的审查。

按风险等级划分的人工批准要求

风险等级由“改动内容”决定,而不是由“作者是谁”决定。一个分类器会根据改动内容判定风险级别;另一个分类器则会判断某次特定改动是否确实需要额外的人工批准——哪怕它所属的风险等级本身并未强制要求。有些路径始终需要额外批准,包括负责人标记为敏感的任何代码,以及所有的 CI 工作流文件。

只有在仓库受信任名单(trusted list)上的作者才能享受放宽的审查要求,这批人通常是负责该仓库的团队成员。其他所有人都需要第二个人来批准。Agent 的批准不能算数,无法替代人工批准。自动审查可以放行低风险改动,但在需要人类把关的地方,它不能代替人类。

改动的作者要对改动负责,因此如果一个 PR 是由 Agent 编写的,操作该 Agent 的人始终会被计入必需的审查者之一。一次 PR 批准仅对当前这一个 commit 有效。如果后续推入了无法直接信任的新 commit(比如 Agent 又推送了新代码,或者有人手动解决了合并冲突),这个 PR 就必须重新获得批准。

6. 合并队列、功能开关与分阶段发布

合并队列(Merge queue)能确保所有检查都在应用了该改动的 main 分支上运行通过,从而防止两个各自通过检查的 PR 合并到一起后把 main 分支搞挂。通常,新功能都会放在功能开关(feature flag)后面发布,并优先向 Anthropic 内部员工开放。因为我们自己重度使用这款产品,所以我们往往能在外部用户之前发现回归问题(regressions)。

当我们面向公众发布时,改动会先进入 latest 渠道,stable 渠道则会滞后跟进。

7. 快速反馈与回滚

总会有一些有问题的合并不可避免地漏过去。这是我们的最后一道防线,核心在于尽可能快地识别出这些问题。位于功能开关后面的改动无需重新部署即可直接关闭。

Claude Code 实行每周轮换的值班(on-call)制度,任何故障复盘(incident review)都会回溯检查当初是哪一层防线本该拦住这个问题。除了监控回滚与故障仪表盘之外,目前仍然要靠作者和值班工程师来留意有问题的合并。我们还没有建立根据这些指标自动收紧批准要求的熔断机制(circuit breaker),但随着我们一步步减少合并环节的人工干预,这也是我们正在探索的方向。

有位团队成员提到,放慢速度、减少产出确实能提高准确率,但那主要是因为总体工作量变少了。我们的优先方向,始终是提升快速发现和修复问题的能力。

我们如何让审查系统变得更好

我们的代码审查流程处于持续改进的循环之中:

我们把线上故障作为评估指标(eval)。团队收集了引发故障的 PR、对应的向前修复(fix-forwards)以及回滚记录,并请熟悉代码库的工程师核实每个 bug 的具体位置与准确性。审查系统每天都会基于这个样本集进行评估。

我们的故障记录就是评估集

每一次新故障都会补充进这个集合,而对审查系统的每一次改动也都会在整个集合上进行评估。

为了衡量噪声(误报),我们会观察人类对审查评论的反应。如果我们容忍报告大量“假 bug”,想要提高 bug 检出率是很容易的,所以必须对此加以约束。我们会根据 PR 负责人对每条审查评论的处理方式进行分类:修复了问题、拒绝修改、澄清这是有意为之,或是认为无关紧要。此外,我们还会把审查系统放到已知干净无误的 PR 上运行,观察它有多大概率会报出那些反而会导致有害修改的建议。

我们明确的优先级是:第一,检测出真正的 bug;第二,减少审查轮次。

当作者是 Agent 时,处理一条审查评论的成本反而更高。人类只需一秒钟就能忽略掉一条吹毛求疵的细节意见(nit),但如果让 Agent 去处理,就会产生一次新的 commit,进而触发新一轮审查。

审查流程必须能够收敛(converge)。否则,作为作者的 Agent 和作为审查者的 Agent 可能会无限循环下去。我们曾在 CLI 项目中见过一个 PR,因为作者 Agent 和审查 Agent 来回拉锯,一晚上循环了五十多次。那次循环最后是人工叫停的,否则它可能会没完没了地跑下去。

当我们对审查系统反复标记的代码运行 git blame 时,发现其中约 59% 的代码在第一轮审查时就已经存在了。也就是说,审查系统其实很少在后续轮次中发现新引入的 bug,而是在每一轮里又多翻出几个旧问题。

我们自己也加剧了这个问题——因为每一轮审查都可能抓出不同的 bug,我们曾养成默认连跑好几轮审查的习惯。虽然对高风险改动进行多轮迭代审查很有用,但并不是每个 PR 都需要这样做。于是,除了召回率(recall)之外,我们团队开始追踪“通过审查所需的轮次(cycles-to-pass)”,并对重审机制做了如下更新:

  • 限制审查轮次:在经历三轮审查后,审查系统会要求作者重新评估整体实现方案。
  • 首轮之后仅审查新增 diff:初始轮次过后,重审范围仅限于新增的代码改动。
  • 指明 bug 的类别:让作者能够一次性修正同一类的所有问题,而不是只改被点出的那一行。
  • 避免重复报告已知问题。
  • 夜间全量扫描:审查 Agent 还会每晚在当前代码库上运行,帮我们在历史遗留 bug 扩散之前把它们找出来。
能够收敛的审查流程

引入收敛机制后,内部每个 PR 的平均审查轮次显著下降。

人类在环

相比把常规错误的审查自动化,让一位资深工程师停下手头的工作去审查常见错误,是一件代价高昂的事。但在以下任务中,人类审查者依然不可或缺:

  • 这东西我们到底该不该做? 决定要不要做一个功能,并为产品确立愿景、品味和目标。有一个声称已经解决代码审查瓶颈的团队,依然会对来自团队外部的改动进行人工审查,以确保那是值得构建的正确功能。
  • 审计高危代码(计费、自动更新器、CI 工作流)。
  • 架构审查,因为这些选择确立了其他人后续遵循的规范。
  • 核验证据(测试输出、运行过的命令、截图、审查评论)。如果你说不出自动化检查会漏掉什么,就别只求别人点个 👍,你需要的是一次真正的人工审查!
  • 承担责任。每一个上线的改动,无论代码是谁敲出来的,最终都要由一个具体的人来负责。

团队对代码库的认知

代码审查一直承担着“同步代码库变更”的双重作用。人们通常就是通过阅读同事的 diff,来了解代码库发生了什么变化以及它是如何运作的。如果绝大部分阅读工作都交给了机器,大家又该如何学习代码库呢?

对此我们并没有设立任何正式机制。团队里没有任何一个人脑子里装着 Claude Code 的完整心智模型,我们也不认为有人需要这样做。相反,当任何人想要了解代码库的某一部分时,他们会让 Claude 针对那一块具体代码生成一份讲解文档(explainer)。这只需要几分钟,而且生成的讲解永远是最新的。

如果绝大部分代码都是机器读的,人类该如何理解代码?

我们当中的一些人还会更进一步,让 Claude 生成一个小型的可视化交互文档(mini artifact),把这部分代码可视化出来,并用自己喜欢的风格进行讲解。

比起逐行阅读琐碎的 diff(比如一行文案修改,你从中几乎学不到任何东西),把人类的注意力集中在这上面要好得多。为你即将修改的模块生成一份讲解文档,能让你学到很多。

此外,我们默认保持信息公开,在公开的 Slack 频道里工作,大家在构建功能的过程中就能实时看到别人在做什么。

“谁合并,谁负责”

如果你合并了一个中小型改动,结果还是出了问题,团队的普遍预期是由你自己把它修好。这种模式之所以不会失控,主要有以下几个原因:

  • 改动粒度小,易于审查、调试和回滚。
  • 一切皆可回滚。功能开关、代码回滚和分阶段发布,可以在几分钟内逆转绝大多数错误。
  • 爆炸半径决定审查门槛。出错代价越高的代码,会有越多人盯、经过越多验证。
  • 遥测(telemetry)与内部重度使用能让回归问题迅速浮出水面。
  • 不搞追责游戏。修复问题的人之所以负责到底,是因为他们掌握最多的上下文;而故障复盘关注的是系统本身。
  • 公开协作分担责任。团队大部分人都在公开频道里工作,任何人都能看到别人在做什么、随时加入讨论,或者让 Claude 帮忙查找遗漏。非代码负责人也会提供反馈。正是这种开放性,让我们无需在每一行代码上设卡,也能保持高质量。

及时调整方向

有了 Agent,启动一项新工作的成本很低,但有时候你根本不该启动它。常见的情况是:频道里有人指出提议的改动路线行不通(比如引用了之前的决策,或提出了更好的方案),随后作者就会直接关掉 PR 或调整实现方案。

正因为一开始写这段代码很容易,直接关掉 PR 也不会让人觉得有什么损失。关键在于尽早在讨论串里调整方向,而不是等到最后面对一个已经定型的 PR 才投出反对票。

如何落地这套流程

这套方法中的绝大多数组件,你的团队现在就可以直接用起来。

在提交 PR 之前(Before the pull request)

  • 给 Agent 一份它可以自行运行的检查清单,并把这些检查列在一个简短的 CLAUDE.md 文件中
  • 通过 hooks 配置 Agent 自动调用代码格式化工具(formatters)和静态检查工具(linters)
  • 在本地运行 /code-review 和 /security-review,或者让一个拥有全新上下文的子 Agent 审查改动
  • 创建一份 REVIEW.md,用来记录反复出现的审查问题

在每一个 PR 上(On every pull request)

  • 强制执行 CI
  • 更新你的 PR 模板,加入“测试方式(Testing)”、“风险(Risk)”和“如何回滚(How to revert)”
  • 先在一个仓库里引入 AI 审查工具(Code Review for Claude Code,或 Claude Code GitHub Action),对 REVIEW.md 迭代几周后,再推广到更多仓库
  • 加入自动化安全审查
  • 要求在合并前解决所有审查讨论串(review threads)
  • 根据风险决定审查严格程度:常规改动快速过一遍,高风险区域(出错代价高的地方)进行彻底审查。Code Review 提供了 Quick、Standard 和 Deep 三个级别
  • 限制审查轮次;几轮过后,只对新增的 diff 进行审查

批准环节(Approval)

  • 定义风险等级,且基于“改动内容”而非“作者”来定级。一开始先从严要求,随着证据积累再逐步放宽
  • 为受信任的作者保留宽松的批准路径
  • 机器人的批准不能用来充当必需的人工批准;每当有新 commit 推送时,自动驳回(dismiss)已有的批准
  • 指定人类作为控制身份认证(auth)、计费、CI、发布工作流以及安装器相关代码与策略的审批人,并通过 CODEOWNERS 和分支保护规则来强制执行

合并与合并后(Merging and post-merge)

  • 搭建合并队列(merge queue)
  • 尝试使用功能开关(feature flags)
  • 优先向内部用户发布
  • 分阶段灰度发布
  • 建立一键回滚机制
  • 通过记录故障和回滚来构建一套评估集(evals)
  • 每当你修改审查工具的提示词(prompts)、工具或模型时,重新对它进行评分
  • 追踪人力投入,同时将回滚率和故障率作为护栏指标

未来的演进方向

当初发布 Code Review 时,我们的设计初衷是最终的批准决定仍然由人类做出,产品所做的只是发布它发现的问题。但在我们自己的代码仓库里,我们正在往前多走一步:代码负责人正在决定哪些低风险改动可以在自动化检查全部通过后自动合并。而且这条边界还会持续演进。

因此,每当我们的审查系统漏掉了什么问题,我们都会问自己:当时模型的上下文里缺了什么?然后把它沉淀到 CLAUDE.md 文件、某个 Skill 或者一项新的自动检查中。我们的目标始终没有改变:在保证回滚率不高于现有水平的前提下,不断减少每次代码改动所需的人力投入。

如果你也允许 Agent 创建 PR,欢迎告诉我们你已经自动化了哪些检查、哪些地方仍然需要人工介入,以及我们有哪些地方做错了。

注:本文改写自 Anthropic 的原始博客文章,并经 Pangram 检测。