<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://yueningbo.github.io/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://yueningbo.github.io/blog/" rel="alternate" type="text/html" hreflang="zh-CN" /><updated>2026-10-27T02:34:13+00:00</updated><id>https://yueningbo.github.io/blog/feed.xml</id><title type="html">阅读与写作</title><subtitle>一个个人博客：收录值得反复读的文章（按原文排版重排、标明出处），也写自己的东西。</subtitle><author><name>yueningbo</name></author><entry><title type="html">拆解 AI agent 的评估</title><link href="https://yueningbo.github.io/blog/posts/demystifying-evals-for-ai-agents.html" rel="alternate" type="text/html" title="拆解 AI agent 的评估" /><published>2026-10-27T01:00:00+00:00</published><updated>2026-10-27T01:00:00+00:00</updated><id>https://yueningbo.github.io/blog/posts/demystifying-evals-for-ai-agents</id><content type="html" xml:base="https://yueningbo.github.io/blog/posts/demystifying-evals-for-ai-agents.html"><![CDATA[<div class="lede">
  <p>好的评估能让团队更有底气地把 AI agent 发出去。没有评估，团队很容易陷进反应式的循环：问题只在生产环境里暴露，修好一个又冒出一个。评估让问题和行为变化在影响用户之前就可见，而且它的价值会在 agent 的整个生命周期里不断累积。</p>
  <p>正如我们在《Building effective agents》里说过的，agent 会跨很多轮工作：调用工具、改动状态、根据中间结果调整策略。让 AI agent 变得有用的那些特性——自主性、智能、灵活性——同时也让它更难被评估。</p>
  <p>通过内部实践，以及和处在 agent 开发前沿的客户合作，我们摸出了一些更严谨、也更有用的评估设计方法。下面这些做法，在多种 agent 架构和真实部署场景里都验证过。</p>
</div>

<h2 id="structure">一次评估由什么构成</h2>

<p><strong>评估</strong>（eval）就是给 AI 系统做的一次测试：给它一个输入，然后用评分逻辑给输出打分，衡量是否成功。本文讨论的是<strong>自动化评估</strong>——不需要真实用户，在开发过程中就能跑。</p>

<p><strong>单轮评估</strong>很直白：一个提示词、一个回答、一套评分逻辑。早期的 LLM 主要用单轮、非 agent 的评估。随着能力提升，<strong>多轮评估</strong>变得越来越常见。</p>

<figure class="art">
  <div class="fig-head">
    <div class="fig-title">单轮 vs Agent 评估</div>
  </div>
  <div class="fig-draw">
    <svg class="fig-svg" viewBox="0 0 651 340" width="651" height="340" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
  <!-- ===== 单轮 ===== -->
  <text x="2" y="78" font-size="12" font-weight="600" fill="var(--ink-3)">单轮</text>

  <rect x="58" y="20" width="150" height="52" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="70" y="40" font-size="12" font-weight="600" fill="var(--ink)">提示</text>
  <text x="70" y="58" font-size="11" fill="var(--ink-2)">一只 {ANIMAL} 有几只脚？</text>

  <rect x="58" y="82" width="150" height="44" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="70" y="102" font-size="12" font-weight="600" fill="var(--ink)">数据</text>
  <text x="70" y="119" font-size="11" fill="var(--ink-2)">ANIMAL = 猫</text>

  <path d="M208 46 L228 46 L228 104 L208 104" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--ink-3)"/>
  <path d="M228 75 L246 75" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M242 71 L246 75 L242 79" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>

  <rect x="250" y="28" width="100" height="94" rx="8" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="300" y="82" font-size="13" font-weight="600" text-anchor="middle" fill="var(--ink)">LLM</text>

  <path d="M350 75 L384 75" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M380 71 L384 75 L380 79" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <rect x="386" y="53" width="72" height="44" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="422" y="80" font-size="12" text-anchor="middle" fill="var(--ink-2)">回答</text>

  <path d="M458 75 L482 75" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M478 71 L482 75 L478 79" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <rect x="484" y="45" width="162" height="60" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="496" y="68" font-size="12" font-weight="600" fill="var(--ink)">评分逻辑</text>
  <text x="496" y="88" font-size="11" fill="var(--ink-2)">response == 18</text>

  <path d="M2 148 L649 148" fill="none" stroke-width="1" style="stroke:var(--line-soft)"/>

  <!-- ===== Agent ===== -->
  <text x="2" y="250" font-size="12" font-weight="600" fill="var(--ink-3)">Agent</text>

  <rect x="58" y="164" width="150" height="44" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="70" y="184" font-size="12" font-weight="600" fill="var(--ink)">工具</text>
  <text x="70" y="200" font-size="10.5" fill="var(--ink-2)">web_search() · file_edit()</text>

  <rect x="58" y="214" width="150" height="44" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="70" y="234" font-size="12" font-weight="600" fill="var(--ink)">环境</text>
  <text x="70" y="250" font-size="10.5" fill="var(--ink-2)">装了 python · 沙箱上网</text>

  <rect x="58" y="264" width="150" height="44" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="70" y="284" font-size="12" font-weight="600" fill="var(--ink)">任务</text>
  <text x="70" y="300" font-size="10.5" fill="var(--ink-2)">写个 MCP server</text>

  <path d="M208 186 L238 186 L238 266 L208 266" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--ink-3)"/>
  <path d="M238 236 L256 236" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M252 232 L256 236 L252 240" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>

  <rect x="260" y="170" width="150" height="140" rx="8" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="272" y="190" font-size="13" font-weight="600" fill="var(--ink)">Agent</text>
  <text x="272" y="210" font-size="10.5" fill="var(--ink-2)">read_file('file.py')</text>
  <text x="272" y="226" font-size="10.5" fill="var(--ink-2)">web_search(MCP 文档)</text>
  <text x="272" y="242" font-size="10.5" fill="var(--ink-2)">edit_file('file2.py')</text>
  <text x="272" y="258" font-size="10.5" fill="var(--ink-3)">…</text>
  <text x="272" y="274" font-size="10.5" fill="var(--ink-2)">bash(pytest)</text>
  <rect x="272" y="284" width="70" height="18" rx="4" fill="var(--fig-focal)" opacity=".18"/>
  <text x="280" y="297" font-size="10.5" fill="var(--ink)">我做完了！</text>

  <path d="M410 236 L434 236" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M430 232 L434 236 L430 240" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <rect x="436" y="196" width="210" height="80" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="448" y="219" font-size="12" font-weight="600" fill="var(--ink)">评分逻辑</text>
  <text x="448" y="239" font-size="11" fill="var(--ink-2)">跑一堆测试，确认 MCP server</text>
  <text x="448" y="256" font-size="11" fill="var(--ink-2)">真的能用</text>

  <path d="M260 300 L244 300 L244 258 L212 258" fill="none" stroke-width="1" stroke-linejoin="round" stroke-dasharray="4 3" style="stroke:var(--ink-3)"/>
  <path d="M216 254 L212 258 L216 262" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <text x="176" y="328" font-size="10.5" fill="var(--ink-3)">环境更新</text>
</svg>
  </div>
  <figcaption><b>图</b>单轮评估只有一问一答；agent 评估要跨多轮调用工具、改动环境，最后用测试验证结果。</figcaption>
</figure>

<p><strong>Agent 评估</strong>还要更复杂。agent 会跨多轮使用工具，一边改环境状态一边调整策略——这意味着错误会传播、会叠加。前沿模型还能找到超出静态评估想象的解法。比如 Opus 4.5 在做 𝜏2-bench 的一道订票题时，发现了政策里的一个漏洞：按题面它算「失败」了，但实际上它给出了对用户更好的方案。</p>

<p>构建 agent 评估时，我们用下面这套定义：</p>

<ul>
  <li><strong>任务</strong>（task，也叫 problem 或 test case）：一次测试，有明确的输入和成功标准。</li>
  <li><strong>试验</strong>（trial）：对同一个任务的一次尝试。模型的输出每次都不一样，所以我们会跑多次，得到更稳定的结果。</li>
  <li><strong>评分器</strong>（grader）：给 agent 某一维度表现打分的逻辑。一个任务可以挂多个评分器，每个评分器里又可以有多条断言（有时叫 checks）。</li>
  <li><strong>轨迹</strong>（transcript，也叫 trace 或 trajectory）：一次试验的完整记录，包括输出、工具调用、推理过程、中间结果以及其他所有交互。对 Anthropic API 来说，就是评估结束时完整的 messages 数组。</li>
  <li><strong>结果</strong>（outcome）：试验结束时环境的最终状态。订票 agent 可能在轨迹末尾说「您的航班已订好」，但真正的结果是环境数据库里到底有没有这条预订记录。</li>
  <li><strong>评估脚手架</strong>（evaluation harness）：把评估从头跑到尾的基础设施。它提供指令和工具、并发跑任务、记录每一步、给输出打分并汇总结果。</li>
  <li><strong>agent 脚手架</strong>（agent harness，也叫 scaffold）：让模型能以 agent 方式运转的系统——处理输入、编排工具调用、返回结果。我们评估「一个 agent」时，评估的是脚手架<em>和</em>模型一起工作的效果。</li>
  <li><strong>评估套件</strong>（evaluation suite）：为衡量特定能力或行为而设计的一组任务。同一个套件里的任务通常共享一个大致目标，比如客服套件里会有退款、取消、升级等任务。</li>
</ul>

<figure class="art">
  <div class="fig-head">
    <div class="fig-title">评估的组成部分</div>
  </div>
  <div class="fig-draw">
    <svg class="fig-svg" viewBox="0 0 651 344" width="651" height="344" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
  <!-- 评估脚手架 -->
  <rect x="2" y="20" width="498" height="252" rx="10" fill="none" stroke="var(--line)"/>
  <text x="14" y="40" font-size="11" font-weight="600" fill="var(--ink-3)">评估脚手架</text>

  <!-- 评估套件 -->
  <rect x="14" y="50" width="474" height="208" rx="8" fill="var(--bg-raised)" stroke="var(--line-soft)"/>
  <text x="26" y="70" font-size="11" font-weight="600" fill="var(--ink-3)">评估套件</text>

  <!-- 一个任务 -->
  <rect x="26" y="80" width="286" height="166" rx="8" fill="var(--bg-panel)" stroke="var(--fig-focal)" stroke-opacity=".5"/>
  <text x="38" y="100" font-size="12" font-weight="600" fill="var(--ink)">任务：密码为空时的认证绕过</text>

  <text x="38" y="122" font-size="10.5" fill="var(--ink-3)">评分器</text>
  <rect x="38" y="128" width="112" height="20" rx="4" fill="none" stroke="var(--line-strong)"/>
  <text x="46" y="142" font-size="10" fill="var(--ink-2)">deterministic_tests</text>
  <rect x="156" y="128" width="74" height="20" rx="4" fill="none" stroke="var(--line-strong)"/>
  <text x="164" y="142" font-size="10" fill="var(--ink-2)">state_check</text>

  <rect x="38" y="154" width="78" height="20" rx="4" fill="none" stroke="var(--line-strong)"/>
  <text x="46" y="168" font-size="10" fill="var(--ink-2)">llm_rubric</text>
  <rect x="122" y="154" width="74" height="20" rx="4" fill="none" stroke="var(--line-strong)"/>
  <text x="130" y="168" font-size="10" fill="var(--ink-2)">tool_calls</text>

  <text x="38" y="196" font-size="10.5" fill="var(--ink-3)">跟踪指标</text>
  <rect x="38" y="202" width="48" height="20" rx="4" fill="none" stroke="var(--line-strong)"/>
  <text x="46" y="216" font-size="10" fill="var(--ink-2)">轮数</text>
  <rect x="92" y="202" width="64" height="20" rx="4" fill="none" stroke="var(--line-strong)"/>
  <text x="100" y="216" font-size="10" fill="var(--ink-2)">工具调用</text>
  <rect x="162" y="202" width="54" height="20" rx="4" fill="none" stroke="var(--line-strong)"/>
  <text x="170" y="216" font-size="10" fill="var(--ink-2)">token</text>
  <rect x="222" y="202" width="44" height="20" rx="4" fill="none" stroke="var(--line-strong)"/>
  <text x="230" y="216" font-size="10" fill="var(--ink-2)">延迟</text>

  <text x="38" y="236" font-size="10" fill="var(--ink-3)">试验 ×N：每次试验留一条完整轨迹</text>

  <!-- 其余任务 -->
  <rect x="324" y="80" width="72" height="166" rx="8" fill="none" stroke="var(--line-soft)"/>
  <text x="336" y="102" font-size="11" fill="var(--ink-3)">任务</text>
  <text x="336" y="126" font-size="11" fill="var(--ink-3)">…</text>
  <rect x="404" y="80" width="72" height="166" rx="8" fill="none" stroke="var(--line-soft)"/>
  <text x="416" y="102" font-size="11" fill="var(--ink-3)">任务</text>

  <!-- agent 脚手架 -->
  <rect x="514" y="106" width="134" height="62" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="581" y="133" font-size="11.5" font-weight="600" text-anchor="middle" fill="var(--ink)">Agent 脚手架</text>
  <text x="581" y="152" font-size="10" text-anchor="middle" fill="var(--ink-3)">模型 + 工具编排</text>
  <path d="M500 137 L512 137" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M508 133 L512 137 L508 141" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <path d="M512 145 L500 145" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M504 141 L500 145 L504 149" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>

  <!-- 结果 -> 打分 -->
  <path d="M200 272 L200 286" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M196 282 L200 286 L204 282" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <rect x="130" y="288" width="150" height="46" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="142" y="307" font-size="12" font-weight="600" fill="var(--ink)">结果</text>
  <text x="142" y="323" font-size="10.5" fill="var(--ink-2)">试验结束时的环境状态</text>

  <path d="M280 311 L308 311" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M304 307 L308 311 L304 315" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <rect x="312" y="288" width="230" height="46" rx="8" fill="var(--bg-raised)" stroke="var(--line)"/>
  <text x="324" y="307" font-size="12" font-weight="600" fill="var(--ink)">评分器打分</text>
  <text x="324" y="323" font-size="10.5" fill="var(--ink-2)">轨迹 + 结果 → 分数</text>
</svg>
  </div>
  <figcaption><b>图</b>脚手架跑套件，套件装任务，任务挂评分器和指标；每次执行留一条轨迹，最后对结果打分。</figcaption>
</figure>

<h2 id="why">为什么要建评估</h2>

<p>团队刚开始做 agent 时，靠手动测试、自己吃自己的狗粮、加上直觉，往往能走得比想象中远。更严谨的评估看起来像是拖慢交付的额外开销。但过了原型阶段、agent 上了生产开始放量之后，没有评估的开发方式就开始崩了。</p>

<p>崩溃点通常出现在这里：用户反馈「改完之后感觉变差了」，而团队完全在瞎飞——除了猜和试，没有任何验证手段。没有评估，调试就是反应式的：等投诉、手动复现、修掉、祈祷别的地方没被弄坏。团队分不清真正的回归和噪声，没法在发布前拿几百个场景自动验证改动，也量不出改进。</p>

<p>这个过程我们见过很多次。Claude Code 起步时，靠 Anthropic 内部员工和外部用户的反馈快速迭代；后来才开始加评估——先是很窄的领域，比如简洁度、文件编辑，再到更复杂的行为，比如过度设计。这些评估帮忙定位问题、指导改进、聚焦研究与产品的协作。再加上生产监控、A/B 测试、用户研究，评估给了 Claude Code 在规模变大时继续改进的信号。</p>

<p>在 agent 生命周期的任何阶段，写评估都有用。早期，评估逼着产品团队把「什么叫成功」说清楚；后期，它帮团队守住一致的质量线。</p>

<p>Descript 的 agent 帮用户剪视频，他们围绕成功剪辑流程的三个维度建了评估：别弄坏东西、按我说的做、做好。评分方式从人工打分演进到 LLM 评分（判据由产品团队定义、定期用人工校准），现在他们常态化跑两套：一套做质量基准，一套做回归测试。Bolt 的 AI 团队起步更晚，那时他们的 agent 已经有大量用户；三个月里，他们搭出了一套评估系统：跑 agent、用静态分析给输出打分、用浏览器 agent 测应用、用 LLM 裁判评「是否遵循指令」这类行为。</p>

<p>有的团队在开发之初就建评估，有的等到上规模、评估成了改进瓶颈时才补。评估在开发早期尤其有用：它把期望的行为显式写下来。两个工程师读同一份初始 spec，对「AI 该怎么处理边界情况」可能理解不同，一套评估用例就能消除这种歧义。无论什么时候建，评估都能加速开发。</p>

<p>评估也决定你能多快换上新模型。更强的模型出来时，没有评估的团队要花几周测试，而有评估的对手几天内就能摸清新的强弱、调好提示词、完成升级。</p>

<p>一旦有了评估，等于免费获得基线和回归测试：延迟、token 用量、单任务成本、错误率，都可以在一组固定任务上持续跟踪。评估还能成为产品团队和研究团队之间带宽最高的沟通渠道，定义出研究员可以优化的指标。评估的好处远不止盯回归——只是它的成本在前、收益在后，复利效应容易被忽略。</p>

<h2 id="how">如何评估 AI agent</h2>

<p>今天大规模部署的 agent 主要有几类：编码 agent、研究 agent、计算机操作 agent、对话 agent。类型之间行业差异很大，但评估技术可以通用。你不需要从零发明一套评估方法——下面几节讲的是各类 agent 上验证过的做法，可以直接当作地基，再扩展到你的领域。</p>

<h3>agent 的评分器类型</h3>

<p>agent 评估通常混合三种评分器：基于代码的、基于模型的、人工的。每种评分器评的是轨迹或结果的一部分。有效评估设计的关键之一，是为活选对评分器。</p>

<div class="table-wrap table-wrap--wide">
  <table>
    <thead>
      <tr><th>评分器</th><th>方法</th><th>优势</th><th>局限</th></tr>
    </thead>
    <tbody>
      <tr>
        <td>基于代码</td>
        <td>字符串匹配（精确、正则、模糊等）；二元测试（fail-to-pass、pass-to-pass）；静态分析（lint、类型、安全）；结果校验；工具调用校验（用了哪些工具、参数对不对）；轨迹分析（轮数、token 用量）</td>
        <td>快；便宜；客观；可复现；容易调试；能验证特定条件</td>
        <td>对「与预期不完全一致的合理变体」很脆；缺乏细微度；不太适合评估偏主观的任务</td>
      </tr>
      <tr>
        <td>基于模型</td>
        <td>按评分量表打分；自然语言断言；两两对比；参照式评估；多裁判共识</td>
        <td>灵活；可扩展；能捕捉细微差别；能处理开放式任务；能处理自由格式输出</td>
        <td>不确定；比代码贵；要和人工评分对齐校准，才谈得上准确</td>
      </tr>
      <tr>
        <td>人工</td>
        <td>领域专家评审；众包判断；抽样抽查；A/B 测试；标注者一致性</td>
        <td>金标准质量；贴近专家用户的判断；用来校准模型评分器</td>
        <td>贵；慢；通常需要能规模化触达的人类专家</td>
      </tr>
    </tbody>
  </table>
</div>

<p>每个任务的计分方式可以是加权的（多个评分器得分合起来要过阈值）、二元的（所有评分器都必须通过），或者混合的。</p>

<h3>能力型评估与回归型评估</h3>

<p><strong>能力型（质量型）评估</strong>问的是「这个 agent 能把什么做好」。它应该从很低的通过率开始，瞄准 agent 还做不好的任务，给团队一座要爬的山。</p>

<p><strong>回归型评估</strong>问的是「它原来会做的那些事，现在还做得好吗」，通过率应该接近 100%。它防止倒退：分数掉了，就说明有东西坏了、需要修。团队在能力型评估上往上爬的时候，一定要同时跑回归型评估，确保改动没有在别处闯祸。</p>

<p>agent 上线并调优之后，那些通过率已经很高的能力型评估可以「毕业」，变成持续运行的回归套件，用来发现漂移。曾经衡量「我们到底能不能做到」的任务，这时衡量的是「我们能不能稳定地做到」。</p>

<h3>评估编码 agent</h3>

<p><strong>编码 agent</strong>写代码、跑测试、调 bug，像人类开发者一样在代码库里穿行、执行命令。对现代编码 agent 有效的评估，通常依赖任务描述清晰、测试环境稳定、对生成代码的测试足够全面。</p>

<p>确定性评分器天然适合编码 agent，因为软件好不好评是很直白的：代码跑不跑得起来、测试过不过。两个被广泛使用的基准就走的这条路：SWE-bench Verified 把热门 Python 仓库的 GitHub issue 交给 agent，用跑测试套件的方式打分——修好失败的测试、同时不弄坏已有测试，才算通过。一年之内，LLM 在这个评估上的成绩从 40% 涨到了 80% 以上。Terminal-Bench 走另一条线：测端到端的技术任务，比如从源码编译 Linux 内核、训练一个 ML 模型。</p>

<p>当你手上有一组能判断编码任务关键<em>结果</em>的通过/失败测试之后，通常还值得把轨迹也评一遍。比如基于启发式规则的代码质量检查，可以依据「测试通过」之外的东西来评价生成的代码；带清晰评分量表的模型评分器，可以评 agent 怎么调工具、怎么和用户互动。</p>

<p><strong>示例：一个编码 agent 的理论评估</strong></p>

<p>设想一个任务：agent 要修掉一个认证绕过漏洞。下面这份示意性的 YAML 展示了如何用多个评分器和指标来评估它。</p>

<pre><span class="tag">yaml</span><code>task:
  id: "fix-auth-bypass_1"
  desc: "Fix authentication bypass when password field is empty and ..."
  graders:
    - type: deterministic_tests
      required: [test_empty_pw_rejected.py, test_null_pw_rejected.py]
    - type: llm_rubric
      rubric: prompts/code_quality.md
    - type: static_analysis
      commands: [ruff, mypy, bandit]
    - type: state_check
      expect:
        security_logs: {event_type: "auth_blocked"}
    - type: tool_calls
      required:
        - {tool: read_file, params: {path: "src/auth/*"}}
        - {tool: edit_file}
        - {tool: run_tests}
  tracked_metrics:
    - type: transcript
      metrics:
        - n_turns
        - n_toolcalls
        - n_total_tokens
    - type: latency
      metrics:
        - time_to_first_token
        - output_tokens_per_sec
        - time_to_last_token</code></pre>

<p>注意，这个例子把可用的评分器铺满，是为了演示。实践中，编码评估通常靠单元测试验正确性、靠一个 LLM 评分量表评整体代码质量，其余评分器和指标按需再加。</p>

<h3>评估对话 agent</h3>

<p><strong>对话 agent</strong>在客服、销售、教练这类领域与用户交流。和传统聊天机器人不同，它们维护状态、调用工具、在对话中途执行动作。编码和研究 agent 同样会与用户多轮交互，但对话 agent 有个独特的挑战：<em>交互本身的质量</em>就是你要评的东西之一。有效的评估通常依赖可验证的终态结果，加上能同时反映任务完成度和交互质量的评分量表。和大多数评估不同，它们往往需要第二个 LLM 来扮演用户。</p>

<p>对对话 agent 来说，成功可以是多维的：工单解决了吗（状态检查）？十轮之内完成了吗（轨迹约束）？语气合适吗（LLM 评分量表）？𝜏-Bench 和它的后继者 τ2-Bench 就体现了这种多维性：它们在零售客服、机票预订等领域模拟多轮交互，一个模型扮演用户人设，另一个 agent 在贴近真实的场景里应对。</p>

<p><strong>示例：一个对话 agent 的理论评估</strong></p>

<p>设想一个客服任务：agent 要处理一位不满意客户的退款。</p>

<pre><span class="tag">yaml</span><code>graders:
  - type: llm_rubric
    rubric: prompts/support_quality.md
    assertions:
      - "Agent showed empathy for customer's frustration"
      - "Resolution was clearly explained"
      - "Agent's response grounded in fetch_policy tool results"
  - type: state_check
    expect:
      tickets: {status: resolved}
      refunds: {status: processed}
  - type: tool_calls
    required:
      - {tool: verify_identity}
      - {tool: process_refund, params: {amount: "&lt;=100"}}
      - {tool: send_confirmation}
  - type: transcript
    max_turns: 10
tracked_metrics:
  - type: transcript
    metrics:
      - n_turns
      - n_toolcalls
      - n_total_tokens
  - type: latency
    metrics:
      - time_to_first_token
      - output_tokens_per_sec
      - time_to_last_token</code></pre>

<p>同样，这个任务铺开多种评分器是为了演示。实践中，对话 agent 评估通常用基于模型的评分器同时评沟通质量和目标达成度，因为很多任务——比如回答一个问题——本来就可能有多个「正确」答案。</p>

<h3>评估研究 agent</h3>

<p><strong>研究 agent</strong>收集、综合、分析信息，产出答案或报告。编码 agent 有单元测试给出二元信号，研究质量却只能相对任务来判。什么算「全面」「来源可靠」，甚至「正确」，都取决于场景：市场扫描、并购尽调、科研报告，标准各不相同。</p>

<p>研究评估有它特有的难点：专家之间对「综合得够不够全」可能就有分歧；随着参考内容变化，真值本身也在漂移；输出越长越开放，出错的空间也越大。像 BrowseComp 这样的基准，测的是 AI agent 能不能在开放网络里大海捞针——题目被设计成容易验证、极难解出。</p>

<p>一个可行的思路是组合多种评分器：<em>有据可依</em>检查论断是否有检索到的来源支撑；<em>覆盖度</em>检查一个好的答案必须包含哪些关键事实；<em>来源质量</em>检查引用的来源是否权威，而不是「第一个检索到的」。对客观题（「X 公司 Q3 营收多少」），精确匹配就够。LLM 可以标出没有支撑的论断和覆盖缺口，也能评估开放式综述的连贯性和完整性。</p>

<p>鉴于研究质量的主观性，基于 LLM 的评分量表应该经常用人类专家的判断来校准，才谈得上有效。</p>

<h3>计算机操作 agent</h3>

<p><strong>计算机操作 agent</strong>通过和人类相同的界面与软件交互——截图、点击、键盘输入、滚动——而不是走 API 或执行代码。它们能用任何带图形界面的应用，从设计工具到老式企业软件。评估要求在真实或沙箱环境里把 agent 跑起来，让它操作软件，然后检查是否达成了预期结果。WebArena 就用 URL 和页面状态检查来验证 agent 是否走对了路径，对会改数据的任务再加一层后端状态验证（确认订单真的下了，而不只是出现了确认页）。OSWorld 把范围扩到整个操作系统：任务完成后用脚本检查各种产物——文件系统状态、应用配置、数据库内容、UI 元素属性。</p>

<p>浏览器 agent 要在 token 效率和延迟之间取舍。基于 DOM 的交互执行快，但很吃 token；基于截图的交互慢，但更省 token。比如让 Claude 总结维基百科，从 DOM 里提文本更高效；而在 Amazon 上找一个笔记本内胆包，截图更高效（把整个 DOM 提取出来太贵）。在 Claude for Chrome 里，我们建了评估来检查 agent 是否为每个场景选了合适的工具，这让浏览器任务跑得更快也更准。</p>

<h3>怎么看待评估里的非确定性</h3>

<p>无论哪类 agent，行为每次运行都不一样，这让结果比表面上更难解读。每个任务有自己的成功率——这个任务 90%，那个 50%——这次通过的任务下次可能就挂了。有时候我们真正想量的是：agent 在一个任务上成功的<em>比例</em>。</p>

<p>两个指标能刻画这件事：</p>

<p><strong>pass@k</strong> 衡量 agent 在 <em>k</em> 次尝试里至少蒙对一次的概率。k 越大，pass@k 越高：射门次数多，至少进一个的机会就大。pass@1 为 50%，意思是模型第一次尝试就解出了一半任务。编码场景里我们通常最关心第一次就解出来——pass@1。另一些场景里，只要有一个方案能用，提很多方案也没问题。</p>

<p><strong>pass^k</strong> 衡量 <em>k</em> 次试验全部成功的概率。k 越大，pass^k 越低，因为要求更多次都保持一致是更难的门槛。假设单次成功率 75%，跑 3 次全过的概率是 0.75³ ≈ 42%。对面客的 agent，这个指标尤其重要——用户期待每次都是可靠的。</p>

<figure class="art">
  <div class="fig-head">
    <div class="fig-title">pass@k 与 pass^k 的分岔</div>
  </div>
  <div class="fig-draw">
    <svg class="fig-svg" viewBox="0 0 651 262" width="651" height="262" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
  <!-- 网格与坐标轴 -->
  <path d="M60 30 L60 210 M60 210 L630 210" fill="none" stroke-width="1" style="stroke:var(--line-strong)"/>
  <path d="M60 120 L630 120" fill="none" stroke-width="1" stroke-dasharray="3 4" style="stroke:var(--line-soft)"/>
  <path d="M60 30 L630 30" fill="none" stroke-width="1" stroke-dasharray="3 4" style="stroke:var(--line-soft)"/>

  <text x="52" y="214" font-size="10" text-anchor="end" fill="var(--ink-3)">0%</text>
  <text x="52" y="124" font-size="10" text-anchor="end" fill="var(--ink-3)">50%</text>
  <text x="52" y="34" font-size="10" text-anchor="end" fill="var(--ink-3)">100%</text>

  <text x="70" y="226" font-size="10" text-anchor="middle" fill="var(--ink-3)">1</text>
  <text x="131" y="226" font-size="10" text-anchor="middle" fill="var(--ink-3)">2</text>
  <text x="192" y="226" font-size="10" text-anchor="middle" fill="var(--ink-3)">3</text>
  <text x="253" y="226" font-size="10" text-anchor="middle" fill="var(--ink-3)">4</text>
  <text x="314" y="226" font-size="10" text-anchor="middle" fill="var(--ink-3)">5</text>
  <text x="376" y="226" font-size="10" text-anchor="middle" fill="var(--ink-3)">6</text>
  <text x="437" y="226" font-size="10" text-anchor="middle" fill="var(--ink-3)">7</text>
  <text x="498" y="226" font-size="10" text-anchor="middle" fill="var(--ink-3)">8</text>
  <text x="559" y="226" font-size="10" text-anchor="middle" fill="var(--ink-3)">9</text>
  <text x="620" y="226" font-size="10" text-anchor="middle" fill="var(--ink-3)">10</text>
  <text x="600" y="244" font-size="10" text-anchor="end" fill="var(--ink-3)">试验次数 k</text>

  <!-- 曲线 -->
  <polyline points="70,75 131,41.2 192,32.8 253,30.7 314,30.2 376,30 437,30 498,30 559,30 620,30"
            fill="none" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--fig-focal)"/>
  <polyline points="70,75 131,108.8 192,134.1 253,153 314,167.3 376,178 437,186 498,192 559,196.5 620,199.9"
            fill="none" stroke-width="2" stroke-dasharray="5 4" stroke-linejoin="round" stroke-linecap="round"
            style="stroke:var(--ink-3)"/>


  <!-- 图例 -->
  <line x1="70" y1="14" x2="92" y2="14" stroke-width="2" style="stroke:var(--fig-focal)"/>
  <text x="98" y="18" font-size="10.5" fill="var(--ink-2)">pass@k：至少一次成功</text>
  <line x1="252" y1="14" x2="274" y2="14" stroke-width="2" stroke-dasharray="5 4" style="stroke:var(--ink-3)"/>
  <text x="280" y="18" font-size="10.5" fill="var(--ink-2)">pass^k：k 次全部成功（单次 75%）</text>

  <circle cx="70" cy="75" r="2.5" style="fill:var(--fig-focal)"/>
  <circle cx="314" cy="167.3" r="2.5" style="fill:var(--ink-3)"/>
</svg>
  </div>
  <figcaption><b>图</b>k 变大时两个指标分道扬镳：pass@k 趋近 100%，pass^k 趋近 0%（单次成功率 75% 的情形）。</figcaption>
</figure>

<p>两个指标都有用，用哪个取决于产品要求：一次成功就够用的工具看 pass@k，一致性至关重要的 agent 看 pass^k。</p>

<h2 id="roadmap">从 0 到 1：做出好评估的路线图</h2>

<p>这一节是我们从「没有评估」走到「评估可信」的实践经验。可以把它当成评估驱动开发的路线图：早点定义成功，把它量清楚，然后持续迭代。</p>

<h3>为第一批评测收集任务</h3>

<p><strong>第 0 步：尽早开始。</strong>我们看到很多团队迟迟不建评估，因为他们觉得需要几百个任务。实际上，从真实失败里挑 20 到 50 个简单任务就是很好的开始。毕竟在 agent 开发早期，系统每一次改动往往效果都很明显，效应量大，样本小也够用。更成熟的 agent 才需要更大、更难的评估套件来检测更小的效应，但一开始最好走 80/20。评估这事拖得越久越难建：早期产品需求天然就能翻译成测试用例；拖久了，你就是在从一个已经跑起来的系统里反推成功标准。</p>

<p><strong>第 1 步：从你已经在手工测的东西开始。</strong>先把你开发时手动的检查搬过来——每次发版前会验的行为、用户常试的任务。如果已经在生产环境，就翻 bug 跟踪器和客服队列。把用户报的失败变成测试用例，能保证套件反映真实使用；按影响面排序，能把力气花在刀刃上。</p>

<p><strong>第 2 步：写无歧义的任务，并配参考解。</strong>任务质量比看上去难把握。一个好的任务，是两位领域专家独立判断会得出一致通过/失败结论的任务。他们自己能做出来吗？做不出来，任务就得改。任务描述里的歧义，最后都会变成指标里的噪声。给模型评分器写判据同理：含糊的评分量表只会产出前后矛盾的判断。</p>

<p>每个任务都应该是「一个正确遵循指令的 agent 能通过」的。这一点很微妙。比如审计 Terminal-Bench 时发现：如果任务让 agent 写脚本但没指定路径，而测试假定了某个特定路径，agent 就可能因为不是自己的错而失败。评分器要检查的一切，都应该在任务描述里说清楚；agent 不该因为规格含糊而失败。对前沿模型来说，在大量试验上通过率为 0（也就是 pass@100 为 0），通常说明任务坏了，而不是 agent 不行——这时该回头检查任务描述和评分器。为每个任务准备一个参考解（一份能通过所有评分器的已知可用输出）很有用：它证明任务可解，也验证了评分器配置是否正确。</p>

<p><strong>第 3 步：构建均衡的问题集。</strong>行为<em>该</em>出现的场景和<em>不该</em>出现的场景都要测。单边的评估只会带来单边的优化。比如你只测「该搜索时是否搜索」，最后可能得到一个什么都去搜的 agent。尽量避免类别不平衡的评估。这一点我们是在给 Claude.ai 做网页搜索评估时亲身体会的：难点在于既不让模型在不该搜的时候去搜，又保住它在合适时机做深入调研的能力。团队从两个方向都建了用例：该搜的查询（比如查天气），和该靠已有知识回答的查询（比如「苹果公司是谁创办的」）。在「该搜不搜」和「不该搜乱搜」之间找平衡很难，提示词和评估都迭代了很多轮。随着新例子出现，我们持续往里加用例来扩大覆盖。</p>

<h3>设计评估脚手架与评分器</h3>

<p><strong>第 4 步：搭一个稳健的评估脚手架和稳定的环境。</strong>评估里的 agent 必须和生产里的 agent 行为大致一致，环境本身也不能引入额外噪声。每次试验都应该从干净环境开始，做到「隔离」。运行之间不必要的共享状态（残留文件、缓存、资源耗尽）会让失败彼此相关——那是基础设施不稳，不是 agent 表现。共享状态也可能虚高成绩：在某些内部评估里，我们观察到 Claude 通过翻看前几次试验留下的 git 历史，在某些任务上获得了不公平的优势。如果多个不同的试验因为同一个环境限制（比如 CPU 内存不足）而失败，它们就不是独立的，评估结果也就不可信。</p>

<p><strong>第 5 步：认真设计评分器。</strong>如前所述，好的评估设计就是为 agent 和任务挑对评分器。我们建议：能用确定性评分器就用；需要灵活性或做不到时用 LLM 评分器；人工评分器留作补充验证，用得克制。</p>

<p>有一种常见的冲动：检查 agent 是否按非常具体的步骤走，比如按顺序调用一串工具。我们发现这种做法太僵硬，会造出很脆的测试——agent 经常能找到评估设计者没想到的合理路径。为了不无谓地惩罚创造力，更好的做法通常是评 agent 产出了什么，而不是它走了哪条路。</p>

<p>对多组件的任务，要设置部分得分。一个正确识别了问题、验证了客户身份、但没能完成退款的客服 agent，明显好过一个一上来就失败的。把这种成功程度的连续谱反映到结果里很重要。</p>

<p>模型评分往往需要仔细迭代才能保证准确。LLM 裁判要跟人类专家紧密校准，你才能确信人工打分和模型打分的分歧很小。为了避免幻觉，给 LLM 一条退路——比如指示它在信息不足时返回「Unknown」。给任务的每个维度写清晰、结构化的评分量表，然后用彼此独立的 LLM 裁判分别评每个维度，而不是让一个裁判评所有维度，也会更有帮助。系统稳健之后，偶尔用人工复核就足够了。</p>

<p>有些评估存在隐蔽的失效模式：即使 agent 表现很好，分数依然很低——因为评分器的 bug、脚手架的约束或任务歧义让 agent 做不出来。再成熟的团队也可能漏掉这些。比如 Opus 4.5 在 CORE-Bench 上最初只有 42%，直到一位 Anthropic 研究员找出了多个问题：评分过于死板（期望「96.124991…」时把「96.12」判为错）、任务描述含糊、以及本来就不可能精确复现的随机性任务。修掉这些 bug、换用限制更少的脚手架之后，分数跳到了 95%。类似地，METR 在他们的时间跨度基准里发现若干配置错误的题目：题目让 agent 优化到某个明确分数阈值，但评分要求超过这个阈值——结果像 Claude 这样照指令做的模型被扣分，而无视目标的模型反而得分更高。仔细复核任务和评分器，能避免这类问题。</p>

<p>让评分器抗绕过、抗作弊。agent 不该能轻易「骗过」评估；任务和评分器应该设计成：通过真的意味着解决了问题，而不是钻了没预料到的空子。</p>

<h3>长期维护与使用评估</h3>

<p><strong>第 6 步：读轨迹。</strong>不读的话，你不会知道评分器到底好不好用。在 Anthropic，我们投入做了查看评估轨迹的工具，并会定期花时间读。任务失败时，轨迹能告诉你：是 agent 真做错了，还是评分器拒绝了一个合理方案。它往往还会暴露出关于 agent 和评估行为的关键细节。</p>

<p>失败应该是「讲得通」的：agent 错在哪、为什么错，都很清楚。分数上不去时，我们需要确信那是 agent 表现的问题，而不是评估的问题。读轨迹是验证「评估测的东西是否真的重要」的方式，也是开发 agent 的一项关键技能。</p>

<p><strong>第 7 步：盯住能力型评估的饱和。</strong>一个 100% 通过的评估只能盯回归，提供不了改进信号。<em>评估饱和</em>指的是 agent 把所有可解任务都做对了，没留下改进空间。比如 SWE-Bench Verified 今年年初还是 30%，现在前沿模型已经逼近饱和，超过 80%。接近饱和时进展也会变慢，因为只剩最难的题目——这会让结果产生误导：能力的大幅提升，在分数上只表现为小幅上涨。代码审查创业公司 Qodo 起初对 Opus 4.5 无感，因为他们的单次编码评估捕捉不到在更长、更复杂任务上的收益；后来他们做了一套新的 agent 评估框架，才看清进步。</p>

<p>我们的原则是：在有人深入看过评估细节、读过一些轨迹之前，不把分数当作定论。如果评分不公平、任务有歧义、合理方案被扣分、或者脚手架限制住了模型，评估就该重做。</p>

<p><strong>第 8 步：靠开放共建和长期维护让套件保持健康。</strong>评估套件是活的资产，需要持续投入和明确的责任人，才有用。</p>

<p>在 Anthropic，我们试过多种维护方式。最有效的是：设立专门的评估团队负责核心基础设施，而领域专家和产品团队贡献大部分任务，并自己跑评估。</p>

<p>对 AI 产品团队来说，拥有并迭代评估，应该像维护单元测试一样日常。团队可能在一个 AI 功能上浪费好几周：早期测试里「能用」，却没满足那些没被说出口的期待——而一套设计良好的评估本可以早早暴露它们。定义评估任务，也是检验「产品需求是否具体到可以开工」的最好方式之一。</p>

<p>我们建议实践评估驱动开发：在 agent 还做不到之前，先用评估定义计划中的能力，然后迭代到做得好为止。内部很多功能今天只是「够用」，本质是在赌模型几个月后的能力；从低通过率起步的能力型评估让这件事变得可见。新模型一发布，跑一遍套件就能看出哪些赌注兑现了。</p>

<p>最贴近产品需求和用户的人，最适合定义成功。以现在的模型能力，产品经理、客户成功、销售都可以用 Claude Code 以 PR 的形式贡献一条评估任务——让他们来，甚至更好地，主动创造条件让他们能来。</p>

<figure class="art">
  <div class="fig-head">
    <div class="fig-title">从 0 到 1 的路线图</div>
  </div>
  <div class="fig-draw">
    <svg class="fig-svg" viewBox="0 0 651 276" width="651" height="276" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
  <!-- 三列底板 -->
  <rect x="2" y="8" width="206" height="248" rx="10" fill="var(--bg-raised)"/>
  <rect x="214" y="8" width="210" height="248" rx="10" fill="var(--bg-raised)"/>
  <rect x="430" y="8" width="219" height="248" rx="10" fill="var(--bg-raised)"/>

  <text x="105" y="34" font-size="11" font-weight="600" text-anchor="middle" fill="var(--ink-3)">用例集开发</text>
  <text x="319" y="34" font-size="11" font-weight="600" text-anchor="middle" fill="var(--ink-3)">脚手架</text>
  <text x="539" y="34" font-size="11" font-weight="600" text-anchor="middle" fill="var(--ink-3)">长期维护</text>

  <!-- 第 1 列 -->
  <rect x="21" y="52" width="166" height="38" rx="7" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="33" y="76" font-size="11" fill="var(--ink)"><tspan font-weight="600" fill="var(--fig-focal)">0</tspan>　尽早开始</text>

  <rect x="21" y="102" width="166" height="38" rx="7" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="33" y="126" font-size="11" fill="var(--ink)"><tspan font-weight="600" fill="var(--fig-focal)">1</tspan>　从手工测试开始</text>

  <rect x="21" y="152" width="166" height="38" rx="7" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="33" y="176" font-size="11" fill="var(--ink)"><tspan font-weight="600" fill="var(--fig-focal)">2</tspan>　任务写得无歧义</text>

  <rect x="21" y="202" width="166" height="38" rx="7" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="33" y="226" font-size="11" fill="var(--ink)"><tspan font-weight="600" fill="var(--fig-focal)">3</tspan>　正例反例都覆盖</text>

  <path d="M104 90 L104 98" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M100 94 L104 98 L108 94" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <path d="M104 140 L104 148" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M100 144 L104 148 L108 144" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <path d="M104 190 L104 198" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M100 194 L104 198 L108 194" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>

  <!-- 第 2 列 -->
  <rect x="236" y="52" width="166" height="38" rx="7" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="248" y="76" font-size="11" fill="var(--ink)"><tspan font-weight="600" fill="var(--fig-focal)">4</tspan>　搭稳健的脚手架</text>

  <rect x="236" y="102" width="166" height="38" rx="7" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="248" y="126" font-size="11" fill="var(--ink)"><tspan font-weight="600" fill="var(--fig-focal)">5</tspan>　认真设计评分器</text>

  <path d="M319 90 L319 98" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M315 94 L319 98 L323 94" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>

  <!-- 第 3 列 -->
  <rect x="452" y="52" width="166" height="38" rx="7" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="464" y="76" font-size="11" fill="var(--ink)"><tspan font-weight="600" fill="var(--fig-focal)">6</tspan>　读轨迹</text>

  <rect x="452" y="102" width="166" height="38" rx="7" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="464" y="126" font-size="11" fill="var(--ink)"><tspan font-weight="600" fill="var(--fig-focal)">7</tspan>　盯住评估饱和</text>

  <rect x="452" y="152" width="166" height="38" rx="7" fill="var(--bg-panel)" stroke="var(--line)"/>
  <text x="464" y="176" font-size="11" fill="var(--ink)"><tspan font-weight="600" fill="var(--fig-focal)">8</tspan>　长期维护</text>

  <path d="M535 90 L535 98" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M531 94 L535 98 L539 94" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <path d="M535 140 L535 148" fill="none" stroke-width="1" style="stroke:var(--ink-3)"/>
  <path d="M531 144 L535 148 L539 144" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>

  <!-- 跨列 -->
  <path d="M187 221 L204 221 L204 71 L232 71" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--ink-3)"/>
  <path d="M228 67 L232 71 L228 75" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
  <path d="M402 121 L420 121 L420 71 L448 71" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--ink-3)"/>
  <path d="M444 67 L448 71 L444 75" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
</svg>
  </div>
  <figcaption><b>图</b>九个步骤分成三段：用例集开发、搭脚手架、长期维护。</figcaption>
</figure>

<h2 id="holistic">评估与其他方法如何配合</h2>

<p>自动化评估可以在不上生产、不影响真实用户的前提下，让 agent 跑上千个任务。但这只是理解 agent 表现的众多方式之一。完整的图景还包括生产监控、用户反馈、A/B 测试、人工读轨迹和系统化的人工研究。</p>

<div class="table-wrap table-wrap--wide">
  <table>
    <thead>
      <tr><th>方法</th><th>优点</th><th>局限</th></tr>
    </thead>
    <tbody>
      <tr>
        <td><strong>自动化评估</strong><br><span class="dim">不用真实用户，用程序跑测试</span></td>
        <td>迭代快；完全可复现；不影响用户；每次提交都能跑；不用发版就能测大量场景</td>
        <td>前期投入大；产品和模型演进时要持续维护，否则会漂移；如果与真实用法脱节，会造成虚假的信心</td>
      </tr>
      <tr>
        <td><strong>生产监控</strong><br><span class="dim">跟踪线上指标与错误</span></td>
        <td>能反映大规模下的真实用户行为；抓到合成评估漏掉的问题；给出 agent 真实表现的基准事实</td>
        <td>被动：问题先到用户那里你才知道；信号噪声大；要做埋点投入；缺少用于评分的真值</td>
      </tr>
      <tr>
        <td><strong>A/B 测试</strong><br><span class="dim">用真实流量比较变体</span></td>
        <td>衡量真实用户结果（留存、任务完成）；能控制混淆因素；可扩展、系统化</td>
        <td>慢，要几天到几周才能显著，且需要足够流量；只能测你发布的改动；不深读轨迹的话，很难知道指标变化的「为什么」</td>
      </tr>
      <tr>
        <td><strong>用户反馈</strong><br><span class="dim">点踩、bug 报告这类显式信号</span></td>
        <td>能暴露你没想到的问题；自带真实用户的例子；往往和产品目标相关</td>
        <td>稀疏且自选择；偏向严重问题；用户很少解释<em>为什么</em>失败；不自动；主要靠用户发现问题，会对用户造成负面影响</td>
      </tr>
      <tr>
        <td><strong>人工读轨迹</strong><br><span class="dim">人去看 agent 的对话</span></td>
        <td>培养对失败模式的直觉；能抓到自动检查漏掉的细微质量问题；帮助校准「好」长什么样、把握细节</td>
        <td>耗时；不可扩展；覆盖不一致；审阅者疲劳或不同审阅者会影响信号质量；通常只给质性的信号，没有清晰量化</td>
      </tr>
      <tr>
        <td><strong>系统化人工研究</strong><br><span class="dim">受训评分者对输出做结构化打分</span></td>
        <td>多位人类评分者给出的金标准判断；能处理主观或含糊的任务；为改进模型评分器提供信号</td>
        <td>相对贵、周期长；难以高频进行；评分者之间分歧需要调和；法律、金融、医疗这类复杂领域需要人类专家来主持</td>
      </tr>
    </tbody>
  </table>
</div>

<p>这些方法对应 agent 开发的不同阶段。自动化评估在发布前和 CI/CD 里最有用，每次 agent 改动和模型升级都跑一遍，是质量问题第一道防线。生产监控在上线后接管，用来发现分布漂移和意料之外的真实世界失败。A/B 测试在你有了足够流量之后，验证重大改动。用户反馈和读轨迹是日常工夫，用来填空：持续分类反馈，每周抽样读轨迹，需要时再深挖。系统化的人工研究留给两件事：校准 LLM 评分器，以及评估那些以人类共识为参照的主观输出。</p>

<figure class="art">
  <div class="fig-head">
    <div class="fig-title">质量的瑞士奶酪</div>
  </div>
  <div class="fig-draw">
    <svg class="fig-svg" viewBox="0 0 651 300" width="651" height="300" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
  <!-- 三层，彼此错开 -->
  <rect x="40" y="26" width="230" height="154" rx="10" fill="var(--fig-focal)" fill-opacity=".14" stroke="var(--fig-focal)" stroke-opacity=".4"/>
  <rect x="120" y="76" width="230" height="154" rx="10" fill="var(--fig-focal)" fill-opacity=".08" stroke="var(--fig-focal)" stroke-opacity=".28"/>
  <rect x="200" y="126" width="230" height="154" rx="10" fill="var(--bg-panel)" stroke="var(--line)"/>

  <!-- 孔洞：贴在箭头路径上的三个，是「穿过去」的缺口 -->
  <g fill="var(--bg)" stroke="var(--ink-3)" stroke-width="1">
    <circle cx="80" cy="82" r="15"/>
    <circle cx="200" cy="138" r="15"/>
    <circle cx="330" cy="198" r="15"/>
    <circle cx="150" cy="52" r="11"/>
    <circle cx="250" cy="100" r="12"/>
    <circle cx="90" cy="150" r="12"/>
    <circle cx="180" cy="192" r="11"/>
    <circle cx="400" cy="170" r="11"/>
    <circle cx="280" cy="245" r="12"/>
    <circle cx="380" cy="252" r="10"/>
  </g>

  <!-- 一个漏过去的失败 -->
  <path d="M10 50 L420 240" fill="none" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--fig-focal)"/>
  <path d="M407 239.5 L420 240 L411.2 230.5" fill="none" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--fig-focal)"/>

  <!-- 层名 -->
  <text x="170" y="44" font-size="10.5" font-weight="600" fill="var(--ink-2)">自动评估</text>
  <text x="270" y="94" font-size="10.5" font-weight="600" fill="var(--ink-2)">人工读轨迹</text>
  <text x="216" y="272" font-size="10.5" font-weight="600" fill="var(--ink-2)">生产监控 / A/B / 用户反馈</text>

  <!-- 右侧说明 -->
  <g>
    <circle cx="452" cy="52" r="4" fill="none" stroke="var(--fig-focal)"/>
    <text x="466" y="48" font-size="10.5" font-weight="600" fill="var(--ink)">自动评估</text>
    <text x="466" y="64" font-size="10" fill="var(--ink-3)">一致的度量，建立基线，</text>
    <text x="466" y="78" font-size="10" fill="var(--ink-3)">上线前抓住回归</text>

    <circle cx="452" cy="128" r="4" fill="none" stroke="var(--ink-3)"/>
    <text x="466" y="124" font-size="10.5" font-weight="600" fill="var(--ink)">人工读轨迹</text>
    <text x="466" y="140" font-size="10" fill="var(--ink-3)">抓住细微失败，以及</text>
    <text x="466" y="154" font-size="10" fill="var(--ink-3)">意外的用户或 agent 行为</text>

    <circle cx="452" cy="204" r="4" fill="none" stroke="var(--ink-3)"/>
    <text x="466" y="200" font-size="10.5" font-weight="600" fill="var(--ink)">生产监控 / A/B / 反馈</text>
    <text x="466" y="216" font-size="10" fill="var(--ink-3)">在大规模下暴露稀有边界</text>
    <text x="466" y="230" font-size="10" fill="var(--ink-3)">与真实用量</text>
  </g>
</svg>
  </div>
  <figcaption><b>图</b>没有哪一层能拦下所有问题；多层叠加，从这一层漏掉的会被另一层接住。</figcaption>
</figure>

<p>最有效的团队会把这些方法组合起来：自动化评估负责快速迭代，生产监控提供真值，定期人工复核做校准。</p>

<h2 id="conclusion">结语</h2>

<p>没有评估的团队会陷在反应式循环里：修掉一个失败，又造出另一个，还分不清真正的回归和噪声。早早投入的团队恰恰相反：失败变成用例，用例挡住回归，指标取代猜测，开发反而更快。评估给整个团队一座看得见的山，把「感觉变差了」变成可执行的东西。它的价值会复利——但前提是你把评估当成核心部件，而不是事后补丁。</p>

<p>具体做法随 agent 类型不同而变，但底层原则不变：尽早开始，别等一套完美的套件；从你看到的失败里收集真实任务；写出无歧义、稳健的成功标准；认真设计评分器并组合多种类型；确保题目对模型来说足够难；持续迭代评估以提升信噪比；然后，读轨迹。</p>

<p>AI agent 评估仍是一个年轻、快速演进的领域。随着 agent 承担更长的任务、在多 agent 系统里协作、处理越来越主观的工作，方法也得跟着变。学到新东西，我们会继续分享。</p>

<h2 id="frameworks">附录：评估框架</h2>

<p>一些开源和商业框架能帮团队不必从零搭基础设施。怎么选取决于 agent 类型、现有技术栈，以及你需要离线评估、生产可观测，还是两者都要。</p>

<ul>
  <li><strong>Harbor</strong>：为在容器化环境里跑 agent 而设计，提供跨云厂商大规模跑试验的基础设施，以及定义任务和评分器的标准格式。Terminal-Bench 2.0 这类热门基准通过 Harbor registry 分发，跑现成基准和自定义套件都很方便。</li>
  <li><strong>Braintrust</strong>：把离线评估、生产可观测和实验跟踪合在一起的平台，适合既要开发期迭代又要盯线上质量的团队。它的 <code>autoevals</code> 库内置了事实性、相关性等常用打分器。</li>
  <li><strong>LangSmith</strong>：提供追踪、离线与在线评估、数据集管理，和 LangChain 生态结合紧密。<strong>Langfuse</strong> 以自托管开源方案提供类似能力，适合有数据驻留要求的团队。</li>
  <li><strong>Arize</strong>：提供 Phoenix——一个开源的 LLM 追踪、调试与离线/在线评估平台，以及 AX，一个把 Phoenix 扩展到规模化、优化和监控的 SaaS 产品。</li>
</ul>

<p>很多团队会混用多个工具、自研评估框架，或者就从简单脚本起步。我们的体会是：框架能加速、能统一，但它的价值取决于你喂进去的任务。更划算的做法通常是先快速选一个贴合工作流的框架，然后把精力花在评估本身——持续打磨高质量用例和评分器。</p>

<p class="thanks">作者：Mikaela Grace、Jeremy Hadfield、Rodrigo Olivares、Jiri De Jonghe。感谢 David Hershey、Gian Segato、Mike Merrill、Alex Shaw、Nicholas Carlini、Ethan Dixon、Pedram Navid、Jake Eaton、Alyssa Baum、Lina Tawfik、Karen Zhou、Alexander Bricken、Sam Kennedy、Robert Ying 等人的贡献。特别感谢在评估合作中让我们学到很多的客户与伙伴。</p>]]></content><author><name>yueningbo</name></author><category term="工程" /><category term="AI 评估" /><category term="Agent" /><category term="工程实践" /><summary type="html"><![CDATA[好的评估能让团队更有底气地把 AI agent 发出去。没有评估，团队很容易陷进反应式的循环：问题只在生产环境里暴露，修好一个又冒出一个。评估让问题和行为变化在影响用户之前就可见，而且它的价值会在 agent 的整个生命周期里不断累积。 正如我们在《Building effective agents》里说过的，agent 会跨很多轮工作：调用工具、改动状态、根据中间结果调整策略。让 AI agent 变得有用的那些特性——自主性、智能、灵活性——同时也让它更难被评估。 通过内部实践，以及和处在 agent 开发前沿的客户合作，我们摸出了一些更严谨、也更有用的评估设计方法。下面这些做法，在多种 agent 架构和真实部署场景里都验证过。]]></summary></entry><entry><title type="html">Claude Code 最佳实践</title><link href="https://yueningbo.github.io/blog/posts/claude-code-best-practices.html" rel="alternate" type="text/html" title="Claude Code 最佳实践" /><published>2026-10-26T02:00:00+00:00</published><updated>2026-10-26T02:00:00+00:00</updated><id>https://yueningbo.github.io/blog/posts/claude-code-best-practices</id><content type="html" xml:base="https://yueningbo.github.io/blog/posts/claude-code-best-practices.html"><![CDATA[<div class="lede">
  <p>Claude Code 是一个 agentic 的编码环境。它不像聊天机器人那样答完就等：它可以读你的文件、跑命令、改代码，自行把问题推进下去——你可以盯着看、随时介入，也可以干脆走开。</p>
  <p>这改变了你的工作方式。你不再是自己写完代码让 Claude 来审，而是描述想要什么，由 Claude 想清楚怎么建：它探索、计划、实现。</p>
  <p>但这种自主性有学习曲线。Claude 工作在若干约束之下，你需要理解它们。</p>
  <p>这份指南整理了在 Anthropic 内部团队，以及各种代码库、语言、环境里的工程师身上反复奏效的做法。</p>
</div>

<div class="callout">
  <span class="label">先记住一个约束</span>
  <p>大多数最佳实践都源自同一个约束：<strong>Claude 的上下文窗口填满得很快，而一旦填满，表现就会下降。</strong></p>
  <p>上下文窗口装着你整段对话：每条消息、Claude 读过的每个文件、每条命令输出。一次调试或一次代码库探索，就可能产生并消耗几万个 token。</p>
  <p>这很重要，因为模型的表现会随上下文填满而下滑。接近上限时，Claude 可能开始「忘记」早先的指令，或者犯更多错。上下文窗口是你最需要管理的资源。</p>
</div>

<h2 id="verify">给 Claude 一个能验证工作的办法</h2>

<div class="callout">
  <span class="label">提示</span>
  <p>给 Claude 一个它能跑的检查：测试、构建、一张用来对比的截图。这决定了你是要一直盯着这个会话，还是能放心走开。</p>
</div>

<p>Claude 在「看起来做完了」的时候会停下。如果没有它能执行的检查，「看起来做完了」就是唯一可用的信号，于是你成了验证环路本身：每一个错误都要等你去发现。给它一个能产出通过或失败的东西，这个环路就自己闭合了——Claude 干活、跑检查、读结果，一直迭代到检查通过。</p>

<p>所谓检查，是任何能在对话里给 Claude 一个可读信号的东西：一套测试、构建的退出码、一个 linter、一个把输出与基准文件做对比的脚本，或者一张与设计稿对比的浏览器截图。在 Claude 的检查通过之后，你自己再跑一遍 <code>/verify</code>，对着运行中的应用确认真的是对的。</p>

<div class="table-wrap table-wrap--wide">
  <table>
    <thead>
      <tr><th>策略</th><th>之前</th><th>之后</th></tr>
    </thead>
    <tbody>
      <tr>
        <td>给出验证标准</td>
        <td>「实现一个校验邮箱地址的函数」</td>
        <td>「写一个 validateEmail 函数。测试用例：user@example.com 为 true，invalid 为 false，user@.com 为 false。实现后跑测试」</td>
      </tr>
      <tr>
        <td>用截图验证 UI 改动</td>
        <td>「把仪表盘弄好看点」</td>
        <td>「[贴截图] 按这个设计实现。把结果截图和原图对比，列差异并修掉」</td>
      </tr>
      <tr>
        <td>解决根因而非症状</td>
        <td>「构建挂了」</td>
        <td>「构建报了这个错：[贴报错]。修掉它并验证构建通过。解决根因，不要压掉错误」</td>
      </tr>
    </tbody>
  </table>
</div>

<p>检查存在之后，再决定它多硬地卡住收尾：</p>

<ul>
  <li><strong>在同一句提示里</strong>：让 Claude 跑检查并在同一条消息里迭代，就像上表那样。</li>
  <li><strong>贯穿整个会话</strong>：把检查设成 <code>/goal</code> 条件。一个独立的评估器在每一轮之后重新检查，Claude 会一直干活直到目标达成。如果 Claude 卡住，Claude Code 最终会带着未达成的目标结束这次运行。</li>
  <li><strong>作为确定性关卡</strong>：Stop hook 会把你的检查当脚本执行，在通过之前不允许这一轮结束。</li>
  <li><strong>交给第二双眼睛</strong>：用一个验证 subagent，或一个会自查结论的动态 workflow，让另一个模型尝试推翻结果——干活的那个 agent 不该同时是打分的那个。</li>
</ul>

<p>每一步都是用配置换注意力。只用提示词的方式今天就能用在任何任务上；<code>/goal</code> 和 Stop hook 才是让无人值守的运行也能正确收尾的东西。</p>

<p>让 Claude 给出证据，而不是宣称成功：测试输出、它跑的命令和返回、或者结果截图。看证据比自己再跑一遍验证更快，而且对你没盯着的会话同样有效。</p>

<h2 id="explore">先探索，再计划，最后写代码</h2>

<div class="callout">
  <span class="label">提示</span>
  <p>把调研与计划跟实现分开，避免「解决了错的问题」。</p>
</div>

<p>让 Claude 直接开写，很可能写出解决错问题的代码。用 plan mode 把探索和执行分开。</p>

<p>推荐的工作流有四个阶段：</p>

<p><strong>1. 探索</strong></p>

<p>按 <code>Shift+Tab</code> 直到状态栏显示 <code>⏸ plan mode on</code> 进入 plan mode，或者用 <code>claude --permission-mode plan</code> 启动会话。Claude 会读文件、回答问题，但不做任何改动。</p>

<pre><span class="tag">claude（plan mode）</span><code>read /src/auth and understand how we handle sessions and login.
also look at how we manage environment variables for secrets.</code></pre>

<p><strong>2. 计划</strong></p>

<p>让 Claude 产出一份详细的实现计划。</p>

<pre><span class="tag">claude（plan mode）</span><code>I want to add Google OAuth. What files need to change?
What's the session flow? Create a plan.</code></pre>

<p>按 <code>Ctrl+G</code> 把计划在编辑器里打开，直接改完再让 Claude 继续。</p>

<p><strong>3. 实现</strong></p>

<p>批准计划或再按 <code>Shift+Tab</code> 退出 plan mode，让 Claude 照着计划写代码并自行对照检查。</p>

<pre><span class="tag">claude</span><code>implement the OAuth flow from your plan. write tests for the
callback handler, run the test suite and fix any failures.</code></pre>

<p><strong>4. 提交</strong></p>

<p>让 Claude 用一条描述清楚的提交信息提交，并开一个 PR。</p>

<pre><span class="tag">claude</span><code>commit with a descriptive message and open a PR</code></pre>

<p>plan mode 有用，但也有开销。范围清晰、改动很小的事情（改个错字、加一行日志、重命名变量），直接让 Claude 做。</p>

<p>计划最适合这些情况：你对接下来的做法没把握；改动会涉及多个文件；你要改的代码自己不熟。如果你能用一句话描述这次 diff，那就跳过计划。</p>

<h2 id="context">在提示词里给出具体上下文</h2>

<div class="callout">
  <span class="label">提示</span>
  <p>指令越精确，需要纠正的次数越少。</p>
</div>

<p>Claude 能推断意图，但读不了你的心。点名具体文件、说明约束、指出可以参考的现成写法。</p>

<div class="table-wrap table-wrap--wide">
  <table>
    <thead>
      <tr><th>策略</th><th>之前</th><th>之后</th></tr>
    </thead>
    <tbody>
      <tr>
        <td><strong>圈定范围。</strong>说清哪个文件、什么场景、测试偏好。</td>
        <td>「给 foo.py 加测试」</td>
        <td>「给 foo.py 写一个测试，覆盖用户已登出的边界情况。不要用 mock」</td>
      </tr>
      <tr>
        <td><strong>指向来源。</strong>把 Claude 引到能回答问题的那份材料上。</td>
        <td>「ExecutionFactory 的 api 怎么这么怪？」</td>
        <td>「翻一下 ExecutionFactory 的 git 历史，总结它的 api 是怎么变成现在这样的」</td>
      </tr>
      <tr>
        <td><strong>参考已有模式。</strong>让 Claude 去看代码库里现成的范例。</td>
        <td>「加一个日历组件」</td>
        <td>「看看首页现有组件是怎么写的，HotDogWidget.php 是个好例子。照这个模式实现一个新的日历组件：可以选择月份，也能前后翻页选年份。除了代码库已有的库，不要引入新依赖」</td>
      </tr>
      <tr>
        <td><strong>描述症状。</strong>给出症状、可能的位置，以及「修好了」长什么样。</td>
        <td>「修一下登录的 bug」</td>
        <td>「用户反馈会话超时后登录失败。检查 src/auth/ 里的认证流程，特别是 token 刷新。先写一个能复现问题的失败测试，再修」</td>
      </tr>
    </tbody>
  </table>
</div>

<p>模糊的提示也有用——尤其是在探索阶段、你付得起来回纠偏的成本时。像「这个文件你会怎么改？」这样的问题，常常能挖出你原本想不到要问的点。</p>

<h3>提供更丰富的内容</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>用 <code>@</code> 引用文件、直接贴截图，或者把数据管道输进去。</p>
</div>

<p>给 Claude 提供资料有几种方式：</p>

<ul>
  <li><strong>用 <code>@</code> 引用文件</strong>，而不是描述代码在哪。Claude 会在回答前先读那个文件。</li>
  <li><strong>直接贴图片</strong>。复制粘贴或拖进提示词即可。</li>
  <li><strong>给 URL</strong>，指向文档和 API 参考。常用域名可以用 <code>/permissions</code> 加进白名单。</li>
  <li><strong>管道输入数据</strong>，例如 <code>cat error.log | claude -p "explain this error"</code>，把文件内容直接送进去。</li>
  <li><strong>让 Claude 自己去取</strong>。告诉它用 Bash 命令、MCP 工具或读文件来拉取上下文。</li>
</ul>

<h2 id="environment">配置好你的环境</h2>

<p>几个设置动作能让 Claude Code 在你所有会话里都明显更好用。想完整了解各种扩展能力以及各自适合什么场景，见官方文档的 Extend Claude Code。</p>

<h3>写好 CLAUDE.md</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>跑一次 <code>/init</code>，它会根据你当前的项目结构生成一份 CLAUDE.md 草稿，然后慢慢打磨。</p>
</div>

<p>CLAUDE.md 是一个特殊文件，Claude 在每次对话开始时都会读它。把 Bash 命令、代码风格、工作流规则写进去，就等于给了 Claude 一份它没法从代码里推断出来的长期上下文。</p>

<p>CLAUDE.md 没有规定格式，但要短、要好读。例如：</p>

<pre><span class="tag">CLAUDE.md</span><code># Code style
- Use ES modules (import/export) syntax, not CommonJS (require)
- Destructure imports when possible (eg. import { foo } from 'bar')

# Workflow
- Be sure to typecheck when you're done making a series of code changes
- Prefer running single tests, and not the whole test suite, for performance</code></pre>

<p>用 <code>/context</code> 确认 Claude 确实加载了它。CLAUDE.md 每个会话都会加载，所以只写普遍适用的东西。只在某些场景才相关的领域知识或流程，用 skills 更合适——Claude 按需加载，不会把每个会话都撑大。</p>

<p>保持精简。对每一行问一句：<em>删掉它，Claude 会不会犯错？</em>不会就砍掉。臃肿的 CLAUDE.md 会让 Claude 忽略你真正的指令。</p>

<div class="table-wrap table-wrap--wide">
  <table>
    <thead>
      <tr><th>✅ 该写</th><th>❌ 不该写</th></tr>
    </thead>
    <tbody>
      <tr><td>Claude 猜不到的 Bash 命令</td><td>Claude 读代码就能知道的事</td></tr>
      <tr><td>与默认风格不同的代码规范</td><td>它本来就懂的语言惯例</td></tr>
      <tr><td>测试方式与偏好的测试运行器</td><td>详细的 API 文档（给链接就好）</td></tr>
      <tr><td>仓库约定（分支命名、PR 规范）</td><td>经常变化的信息</td></tr>
      <tr><td>这个项目特有的架构决策</td><td>长篇解释或教程</td></tr>
      <tr><td>开发环境的坑（必需的环境变量）</td><td>逐个文件描述代码库</td></tr>
      <tr><td>不显然的常见陷阱</td><td>「写干净的代码」这类自明之理</td></tr>
    </tbody>
  </table>
</div>

<p>如果明明写了规则，Claude 还是反复做你不想要的事，多半是文件太长、规则被淹没了。如果 Claude 问你一个 CLAUDE.md 里已经回答过的问题，多半是措辞含糊。把 CLAUDE.md 当代码对待：出问题时回看它，定期修剪，改完观察 Claude 的行为是否真的变了。对已经入库的 CLAUDE.md，跑一次 <code>/doctor</code>，Claude 会提议删掉那些它能从代码库推导出来的内容。</p>

<p>如果某一条指令总被跳过，单独给那一行加「IMPORTANT」之类的强调。强调很多行，等于一行都没强调。把 CLAUDE.md 提交进 git，让团队一起补——这个文件的价值会随时间复利增长。</p>

<p>CLAUDE.md 可以用 <code>@path/to/import</code> 语法导入其他文件。导入规则和文件可以放的位置，见官方文档。</p>

<h3>配置权限</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>想少被问、又不放弃控制权：用 <code>/permissions</code> 预批准你信任的工具，用 <code>/sandbox</code> 让沙箱内的命令免问直接跑。想自己逐条批准改动和命令时，切回 Manual 模式。</p>
</div>

<p>在 Claude Code v2.1.283 及以后，auto 模式是交互式终端与 VS Code 会话的内置起始权限模式：由一个独立的分类模型替你审掉大部分动作，只拦看起来有风险的——比如扩大影响范围、不熟悉的基础设施、或被恶意内容驱动的操作。更早的版本里，只有 Pro、Max 和 Team 套餐默认从 auto 模式起步。</p>

<p>Manual 模式下，会改动系统的动作都要先问你：写文件、Bash 命令、MCP 工具。这安全，但累。批准到第十次时，你其实是在点「同意」而不是在审阅。Manual 模式下有两个东西能减少打断，auto 模式下同样生效：</p>

<ul>
  <li><strong>权限白名单</strong>：批准你确定安全的特定工具，比如 <code>npm run lint</code> 或 <code>git commit</code></li>
  <li><strong>沙箱</strong>：开启操作系统级隔离，限制文件系统与网络访问，让 Claude 在划定的边界内更自由地工作</li>
</ul>

<h3>用 CLI 工具</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>让 Claude Code 通过 <code>gh</code>、<code>aws</code>、<code>gcloud</code>、<code>sentry-cli</code> 这类 CLI 去对接外部服务。</p>
</div>

<p>CLI 工具是与外部服务交互时最省上下文的方式。你用 GitHub 的话，装个 <code>gh</code>：Claude 知道怎么用它开 issue、提 PR、读评论。没有 <code>gh</code> 它也能走 GitHub API，但未认证的请求很容易撞上频率限制。</p>

<p>Claude 学新 CLI 也很在行。可以这样提示：<code>Use 'foo-cli-tool --help' to learn about foo tool, then use it to solve A, B, C.</code></p>

<h3>连接 MCP server</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>用 <code>claude mcp add</code> 加上名字和 URL 或命令，接上 Notion、Figma、你的数据库之类的系统。例如：<code>claude mcp add --transport http notion https://mcp.notion.com/mcp</code>。</p>
</div>

<p>接上 MCP server 之后，你可以让 Claude 按 issue 跟踪器实现功能、查数据库、分析监控数据、对接 Figma 设计稿、自动化一些流程。</p>

<h3>设置 hooks</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>必须每次都发生、零例外的事，用 hooks。</p>
</div>

<p>Hooks 会在 Claude 工作流的特定时点自动跑脚本。CLAUDE.md 里的指令是建议性的，hooks 是确定性的——它保证那件事一定发生。</p>

<p>Hook 也可以让 Claude 来写。可以这样提示：「写一个 hook，每次改完文件都跑 eslint」，或者「写一个 hook，禁止写入 migrations 目录」。手写的话直接改 <code>.claude/settings.json</code>，用 <code>/hooks</code> 查看当前配置了哪些。</p>

<h3>创建 skills</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>在 <code>.claude/skills/</code> 里放 <code>SKILL.md</code>，给 Claude 领域知识和可复用的工作流。</p>
</div>

<p>Skills 用你的项目、团队或领域特有的信息来扩展 Claude 的知识。相关时它会自动用上，你也可以用 <code>/skill-name</code> 直接调用。</p>

<p>在 <code>.claude/skills/</code> 下建一个目录，放一个 <code>SKILL.md</code> 就是一个 skill：</p>

<pre><span class="tag">.claude/skills/api-conventions/SKILL.md</span><code>---
name: api-conventions
description: REST API design conventions for our services
---
# API Conventions
- Use kebab-case for URL paths
- Use camelCase for JSON properties
- Always include pagination for list endpoints
- Version APIs in the URL path (/v1/, /v2/)</code></pre>

<p>Skills 也能定义你直接调用的重复性流程：</p>

<pre><span class="tag">.claude/skills/fix-issue/SKILL.md</span><code>---
name: fix-issue
description: Fix a GitHub issue
disable-model-invocation: true
---
Analyze and fix the GitHub issue: $ARGUMENTS.

1. Use `gh issue view` to get the issue details
2. Understand the problem described in the issue
3. Search the codebase for relevant files
4. Implement the necessary changes to fix the issue
5. Write and run tests to verify the fix
6. Ensure code passes linting and type checking
7. Create a descriptive commit message
8. Push and create a PR</code></pre>

<p>用 <code>/fix-issue 1234</code> 触发。有副作用、你想手动触发的流程，加上 <code>disable-model-invocation: true</code>。</p>

<h3>创建自定义 subagent</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>在 <code>.claude/agents/</code> 里定义专门的助手，Claude 可以把独立任务交给它们。</p>
</div>

<p>Subagent 在自己的上下文里运行，带着自己那套允许使用的工具。适合要读很多文件、或者需要专门视角又不想弄脏主对话的任务。</p>

<pre><span class="tag">.claude/agents/security-reviewer.md</span><code>---
name: security-reviewer
description: Reviews code for security vulnerabilities
tools: Read, Grep, Glob, Bash
model: opus
---
You are a senior security engineer. Review code for:
- Injection vulnerabilities (SQL, XSS, command injection)
- Authentication and authorization flaws
- Secrets or credentials in code
- Insecure data handling

Provide specific line references and suggested fixes.</code></pre>

<p>要明确告诉 Claude 用 subagent：「用 subagent 审一下这段代码的安全问题」。</p>

<h3>安装插件</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>用 <code>/plugin</code> 逛市场。插件把 skills、工具和集成打包好，装上就能用，不需要配置。</p>
</div>

<p>插件把 skills、hooks、subagent 和 MCP server 打包成一个可安装单元，来自社区和 Anthropic。如果你用带类型的语言，装一个代码智能插件，能让 Claude 精确跳转符号，并在改动后自动发现错误。</p>

<p>在 skills、subagent、hooks、MCP 之间怎么选，见官方文档的 Extend Claude Code。</p>

<h2 id="communicate">有效地沟通</h2>

<p>像问另一位工程师那样问 Claude；功能比较大时，先让 Claude 反过来采访你、写出一份 spec，再开始实现。</p>

<h3>问代码库的问题</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>把 Claude 当资深工程师来问。</p>
</div>

<p>接手新代码库时，用 Claude Code 来学习和探索。你可以问那些本来要问同事的问题：</p>

<ul>
  <li>日志是怎么打的？</li>
  <li>我要怎么加一个新的 API 端点？</li>
  <li><code>foo.rs</code> 第 134 行的 <code>async move { ... }</code> 是干什么的？</li>
  <li><code>CustomerOnboardingFlowImpl</code> 处理了哪些边界情况？</li>
  <li>第 333 行为什么调 <code>foo()</code> 而不是 <code>bar()</code>？</li>
</ul>

<p>这样用 Claude Code 是很有效的上手方式：缩短爬坡时间，也减轻其他工程师的负担。不需要什么特殊提示，直接问就行。</p>

<h3>让 Claude 采访你</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>功能比较大时，先让 Claude 采访你。用一句很小的提示开头，让它用 <code>AskUserQuestion</code> 工具来问你。</p>
</div>

<p>Claude 会问到你还没想到的地方：技术实现、界面与交互、边界情况、取舍。发送前把 <code>[简述]</code> 换成你的功能。</p>

<pre><span class="tag">claude</span><code>I want to build [brief description]. Interview me in detail using the AskUserQuestion tool.

Ask about technical implementation, UI/UX, edge cases, concerns, and tradeoffs. Don't ask obvious questions, dig into the hard parts I might not have considered.

Keep interviewing until we've covered everything, then write a complete spec to SPEC.md.</code></pre>

<p>spec 写完之后，开一个全新的会话去执行它。新会话的上下文是干净的，全部用于实现；而你手上有一份写好的 spec 可以对照。</p>

<p>最有用的 spec 是自包含的：点名涉及的文件和接口，写明什么不在范围内，最后给一个端到端的验证步骤，证明这个功能确实能用。花在把 spec 写精确上的时间，回报高于花在盯着实现上的时间。</p>

<h2 id="session">管理你的会话</h2>

<p>对话是持久的，也是可回退的。把这两点用起来。</p>

<h3>尽早、频繁地纠偏</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>发现它跑偏就立刻纠正。</p>
</div>

<p>最好的结果来自紧凑的反馈环路。Claude 偶尔一次就做对，但多数时候，快速纠正能更快得到更好的方案。</p>

<ul>
  <li><strong><code>Esc</code></strong>：中断 Claude 正在做的事。上下文会保留，你可以就地改方向。</li>
  <li><strong><code>Esc + Esc</code> 或 <code>/rewind</code></strong>：打开回退菜单，恢复之前的对话与代码状态，或者从某条消息开始做总结。</li>
  <li><strong>「撤销这个」</strong>：让 Claude 撤掉它自己的改动。</li>
  <li><strong><code>/clear</code></strong>：在互不相关的任务之间重置上下文。带着无关内容的长会话会降低表现。</li>
</ul>

<p>如果同一个问题在一个会话里纠正了两次以上，上下文里已经堆满了失败的尝试。跑 <code>/clear</code>，带着你学到的东西写一条更具体的提示重新开始。干净的会话加更好的提示，几乎总是胜过带着一堆纠正的长会话。</p>

<h3>激进地管理上下文</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>互不相关的任务之间跑 <code>/clear</code> 重置上下文。</p>
</div>

<p>接近上限时 Claude Code 会自动压缩对话历史：保留重要的代码和决策，腾出空间。</p>

<p>长会话里，上下文窗口会塞满无关的对话、文件内容和命令，既降低表现，有时还会让 Claude 分心。</p>

<ul>
  <li>任务之间常用 <code>/clear</code>，彻底重置上下文窗口</li>
  <li>自动压缩触发时，Claude 会总结最关键的部分：代码模式、文件状态、关键决策</li>
  <li>想更可控，用 <code>/compact &lt;指令&gt;</code>，比如 <code>/compact Focus on the API changes</code></li>
  <li>只想压缩一段对话，用 <code>Esc + Esc</code> 或 <code>/rewind</code> 选一个消息检查点，然后选择「从这里开始总结」或「总结到这里」：前者压缩该点之后的若干消息、保留早先的上下文；后者压缩早先的消息、完整保留近期内容</li>
  <li>把压缩行为写进 CLAUDE.md，例如「压缩时始终保留完整的改动文件列表和所有测试命令」，保证关键上下文熬过总结</li>
  <li>不需要留在上下文里的问题，用 <code>/btw</code>：答案不会进入对话历史，查个细节而不撑大上下文</li>
</ul>

<h3>用 subagent 做调研</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>用「用 subagent 调研 X」把研究交出去。它们在独立的上下文里探索，主对话留给实现。</p>
</div>

<p>既然上下文是根本约束，就把研究挪出主上下文。Claude 调研一个代码库要读大量文件，全都消耗你的上下文。Subagent 在独立的上下文窗口里跑，只把摘要报回来：</p>

<pre><span class="tag">claude</span><code>Use subagents to investigate how our authentication system handles token
refresh, and whether we have any existing OAuth utilities I should reuse.</code></pre>

<p>Claude 实现完之后，也可以用 subagent 来做验证。</p>

<h3>用 checkpoint 回退</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>每一次会开启新一轮的提示都会创建一个 checkpoint。你可以把对话、代码或两者恢复到任何之前的检查点。</p>
</div>

<p>Claude 在每次改动前会自动给文件做快照，checkpoint 就是靠它恢复。双击 <code>Escape</code> 或跑 <code>/rewind</code> 打开回退菜单：可以只恢复对话、只恢复代码、两者都恢复，或者从选中的消息开始总结。</p>

<p>于是你不必每一步都精心规划：可以让 Claude 试一个冒险的做法，不行就退回来换一条路。Checkpoint 跟对话一起保存，关掉终端、之后恢复会话，仍然能回退。</p>

<div class="callout">
  <span class="label">注意</span>
  <p>Checkpoint 只跟踪通过 Claude 的文件编辑工具做的改动。通过 Bash 命令或外部进程改的东西不会被记录。它不是 git 的替代品。</p>
</div>

<h3>恢复会话</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>用 <code>/rename</code> 给会话命名，像对待分支一样：每条工作线都有自己的持久上下文。</p>
</div>

<p>Claude Code 把对话保存在本地，所以一个任务跨几天也不用重新解释背景。跑 <code>claude --continue</code> 接着上一次，或者 <code>claude --resume</code> 从列表里挑。给会话起描述性名字，比如 <code>oauth-migration</code>，之后好找。</p>

<h2 id="scale">自动化与规模化</h2>

<p>把单个 Claude 用顺之后，用并行会话、非交互模式和扇出模式把产出放大。</p>

<h3>非交互模式</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>在 CI、pre-commit hook 或脚本里用 <code>claude -p "prompt"</code>。加 <code>--output-format stream-json --verbose</code> 得到流式 JSON。</p>
</div>

<p><code>claude -p "your prompt"</code> 让 Claude 非交互地跑，不需要人应答。除非你传 <code>--no-session-persistence</code>，这次运行仍会创建一个可恢复的会话。非交互模式是把 Claude 接进 CI 流水线、pre-commit hook 或任何自动化流程的方式。输出格式让结果可以程序化解析：纯文本、JSON 或流式 JSON。</p>

<pre><span class="tag">bash</span><code># One-off queries
claude -p "Explain what this project does"

# Structured output for scripts
claude -p "List all API endpoints" --output-format json

# Streaming for real-time processing
claude -p "Analyze this log file" --output-format stream-json --verbose</code></pre>

<p>第一条打印纯文本。<code>json</code> 格式返回一个带 <code>result</code> 字段的 JSON 对象。<code>stream-json</code> 每行打印一个 JSON 对象，以一个 init 事件开头。</p>

<h3>并行跑多个会话</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>并行开多个 Claude 会话，加快开发、跑互不干扰的实验，或者启动复杂的工作流。</p>
</div>

<p>按你愿意投入多少协调成本来选并行方式；需要在会话之间传递发现时，再加上消息机制：</p>

<ul>
  <li><strong>Worktree</strong>：在互相隔离的 git 检出里跑各自的 CLI 会话，改动不会互相踩</li>
  <li><strong>跨会话消息</strong>：让你自己开的几个会话互相传递发现</li>
  <li><strong>桌面应用</strong>：可视化地管理多个本地会话，可以各自一个 worktree</li>
  <li><strong>云端 Claude Code</strong>：默认在 Anthropic 托管的基础设施上跑会话</li>
  <li><strong>Agent view</strong>：研究预览。跑 <code>claude agents</code> 派发在后台持续运行的会话，在一个界面里看</li>
  <li><strong>Agent teams</strong>：实验性，默认关闭。多会话自动协同，共享任务、消息，有 team lead</li>
</ul>

<p>除了并行干活，多会话还能支撑偏向质量的工作流。全新的上下文能让代码审查更有效，因为 Claude 不会偏袒自己刚写的代码。</p>

<p>比如 Writer / Reviewer 模式：</p>

<div class="table-wrap table-wrap--wide">
  <table>
    <thead>
      <tr><th>会话 A（作者）</th><th>会话 B（审查者）</th></tr>
    </thead>
    <tbody>
      <tr><td>「给我们的 API 端点实现一个限流器」</td><td></td></tr>
      <tr><td></td><td>「审一下 <code>@src/middleware/rateLimiter.ts</code> 里的限流实现。看边界情况、竞态，以及和现有中间件写法的一致性」</td></tr>
      <tr><td>「这是审查意见：[会话 B 的输出]。把这些都改掉」</td><td></td></tr>
    </tbody>
  </table>
</div>

<p>测试也能这么玩：让一个 Claude 写测试，另一个写代码去通过它们。</p>

<h3>跨文件扇出</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>循环调用 <code>claude -p</code> 逐个处理任务。批量操作时用 <code>--allowedTools</code> 预批准工具。</p>
</div>

<p>大迁移或大分析，可以把工作分摊到许多并行的 Claude 调用上。跑 <code>/batch &lt;指令&gt;</code>，Claude 会把改动拆给 5 到 30 个 subagent，每个在自己的 worktree 里干活。想用自己的脚本驱动扇出，就循环调用 <code>claude -p</code>：</p>

<p><strong>1. 生成任务清单</strong></p>

<p>让 Claude 把需要迁移的文件列表写成文件，下一步的循环就能读它。提示例如：<code>list all 2,000 Python files that need migrating and save the list to files.txt</code></p>

<p><strong>2. 写个脚本遍历这个清单</strong></p>

<pre><span class="tag">bash</span><code>for file in $(cat files.txt); do
  claude -p "Migrate $file from Python 2 to Python 3. Return OK or FAIL." \
    --allowedTools "Edit,Bash(git commit *)" \
    --permission-mode dontAsk
done</code></pre>

<p><strong>3. 先在几个文件上试，再全量跑</strong></p>

<p>根据前两三个文件出的问题改进提示词，然后跑全量。<code>--allowedTools</code> 预批准了迁移需要的工具；<code>--permission-mode dontAsk</code> 会拒绝其他任何需要批准的调用——无人值守时这一点很重要，不过你安装的 mod 仍可能批准其中一些调用。</p>

<p>也可以把 Claude 接进已有的数据处理流水线：</p>

<pre><span class="tag">bash</span><code>claude -p "&lt;your prompt&gt;" --output-format json | your_command</code></pre>

<h3>用 auto 模式自主运行</h3>

<p>想要不被打断、同时后台还有安全检查，用 auto 模式。一个分类模型在命令执行前审查，拦下扩大影响范围、不熟悉的基础设施、被恶意内容驱动的操作，其余常规工作直接放行。</p>

<pre><span class="tag">bash</span><code>claude --permission-mode auto -p "fix all lint errors"</code></pre>

<p>在带 <code>-p</code> 的非交互运行里，如果分类器反复拦截某些操作，Claude Code 不会因此终止这次运行。</p>

<h3>加一步对抗式审查</h3>

<div class="callout">
  <span class="label">提示</span>
  <p>在把任务当作完成之前，让一个 subagent 在全新上下文里审这份 diff，报告缺口。</p>
</div>

<p>Claude 无人值守跑得越久，独立检查就越重要。审查者跑在全新的 subagent 上下文里，只看到 diff 和你给的判据，看不到产生这次改动的推理过程，因此它会按自己的理解评估结果。</p>

<p>想查正确性，跑内置的 <code>/code-review</code> skill：它在全新的 subagent 里审查当前 diff 找 bug，把发现送回会话。想对照计划检查，就自己写审查提示——点名要检查的东西、对照的计划，以及什么算一条发现：</p>

<pre><span class="tag">claude</span><code>Use a subagent to review the rate limiter diff against PLAN.md. Check that
every requirement is implemented, the listed edge cases have tests, and
nothing outside the task's scope changed. Report gaps, not style preferences.</code></pre>

<p>因为审查者以 subagent 运行，实现它的那个会话能直接收到这些缺口，自己修完再重审，不需要你在窗口之间复制粘贴。</p>

<p>被要求「找缺口」的审查者，通常总能报告出一些，即使实现本身是好的——它被要求做的就是这个。追着每一条发现改，会滑向过度设计：多余抽象层、防御性代码、为不可能发生的情况写测试。告诉审查者只报影响正确性或明确需求的缺口，其余当作可选项。</p>

<h2 id="failures">避开常见的失败模式</h2>

<p>这些都是常见的坑，早点认出来能省很多时间：</p>

<ul>
  <li><strong>一锅烩会话。</strong>从一件事开始，中途问了个不相关的，又回第一件事。上下文里全是无关信息。
    <br><em>对策</em>：不相关的任务之间跑 <code>/clear</code>。</li>
  <li><strong>反复纠正。</strong>Claude 做错、你纠正、还是不对、再纠正。上下文被失败的尝试污染。
    <br><em>对策</em>：纠正两次还不行，就跑 <code>/clear</code>，把学到的东西写进一条更好的初始提示。</li>
  <li><strong>过度膨胀的 CLAUDE.md。</strong>文件太长，Claude 会忽略其中一半，因为重要规则被噪声淹没了。
    <br><em>对策</em>：狠心删。Claude 不用这条指令也能做对的，删掉，或者改成 hook。</li>
  <li><strong>信了但没验。</strong>Claude 给出一个看起来像样、其实没处理边界情况的实现。
    <br><em>对策</em>：永远提供验证手段（测试、脚本、截图）。验不了，就别发。</li>
  <li><strong>无限探索。</strong>你让 Claude「调研一下」，没圈范围。它读了几百个文件，把上下文填满。
    <br><em>对策</em>：把调研范围收窄，或者交给 subagent，别让它吃掉主上下文。</li>
</ul>

<h2 id="intuition">培养你的直觉</h2>

<p>这份指南里的做法不是铁律。它们是普遍好用的起点，但不一定适合每种情况。</p>

<p>有时你<em>就该</em>让上下文累积——你正深陷一个复杂问题，历史很有价值。有时你该跳过计划，让 Claude 自己摸——因为任务是探索性的。有时模糊的提示恰好最合适——因为在约束它之前，你想先看看它怎么理解这个问题。</p>

<p>留意什么管用。当 Claude 产出很好，回想你做了什么：提示词的结构、你给的上下文、你当时在哪个模式。当它卡住，问一句为什么：上下文太吵？提示太模糊？任务太大，一次做不完？</p>

<p>慢慢地，你会养出任何指南都写不下来的直觉：什么时候该具体，什么时候该开放；什么时候该计划，什么时候该探索；什么时候该清空上下文，什么时候该让它累积。</p>

<h2 id="related">相关资源</h2>

<ul>
  <li><a href="https://code.claude.com/docs/en/how-claude-code-works" rel="nofollow noopener">How Claude Code works</a>：agentic 环路、工具与上下文管理</li>
  <li><a href="https://code.claude.com/docs/en/features-overview" rel="nofollow noopener">Extend Claude Code</a>：skills、hooks、MCP、subagent 与插件</li>
  <li><a href="https://code.claude.com/docs/en/common-workflows" rel="nofollow noopener">Common workflows</a>：调试、测试、PR 等场景的分步做法</li>
  <li><a href="https://code.claude.com/docs/en/memory" rel="nofollow noopener">CLAUDE.md</a>：存放项目约定与长期上下文</li>
</ul>]]></content><author><name>yueningbo</name></author><category term="工程" /><category term="AI 编码" /><category term="Claude Code" /><category term="工作方法" /><summary type="html"><![CDATA[Claude Code 是一个 agentic 的编码环境。它不像聊天机器人那样答完就等：它可以读你的文件、跑命令、改代码，自行把问题推进下去——你可以盯着看、随时介入，也可以干脆走开。 这改变了你的工作方式。你不再是自己写完代码让 Claude 来审，而是描述想要什么，由 Claude 想清楚怎么建：它探索、计划、实现。 但这种自主性有学习曲线。Claude 工作在若干约束之下，你需要理解它们。 这份指南整理了在 Anthropic 内部团队，以及各种代码库、语言、环境里的工程师身上反复奏效的做法。]]></summary></entry><entry><title type="html">Claude Code 团队如何做代码审查</title><link href="https://yueningbo.github.io/blog/posts/claude-code-code-review.html" rel="alternate" type="text/html" title="Claude Code 团队如何做代码审查" /><published>2026-10-15T01:00:00+00:00</published><updated>2026-10-15T01:00:00+00:00</updated><id>https://yueningbo.github.io/blog/posts/claude-code-code-review</id><content type="html" xml:base="https://yueningbo.github.io/blog/posts/claude-code-code-review.html"><![CDATA[<div class="lede">
      <p>我们之前说过，Claude Code 自身约 90% 的代码是 Claude 写的。那么，在我们这样的产出速度下，代码是怎么被审查的？现在还有人在读代码吗？如果真的出了问题，又是谁发现的？</p>
      <p>这篇文章讲的是 Claude Code 团队的代码审查流程，以及它是如何演进的。从 6 月到 9 月，合并进 Claude Code 代码库的 pull request 数量增长了一倍多，而其中不得不回滚的比例下降了超过 40%。</p>
      <p>我们并没有取消审查。但我们把大部分「阅读」工作交给了机器，围绕人的时间和注意力做优化，并在「让错误能被快速、轻松地撤销」这件事上投入了大量精力。</p>
    </div>

    <h2 id="purpose">重新思考代码审查的目的</h2>

    <p>当写代码的是 agent，变更数量会迅速上升。在 Anthropic 的 monorepo 中，每天落地的变更从今年 <a href="https://www.anthropic.com/engineering/redesigning-source-control">5 月的约 2,500 个增长到 9 月的 30,000 多个</a>。</p>

    <p>Claude Code 住在自己的仓库里，与 monorepo 分离，但它同样承受着巨大压力。以这样的速度交付时，逐行的人工代码审查即使并非不可能，也极其困难。</p>

    <p>代码审查承担两项职责：</p>
    <ol>
      <li>在代码上线前发现并修复问题</li>
      <li>在团队中建立共享的上下文与对代码库的理解</li>
    </ol>

    <p>本文主要聚焦第一项，但最后我们会回到第二项。</p>

    <p>我们认为，「有没有人类读过这段代码？」只是「这次变更可能出什么问题，以及应该由哪道检查——自动的还是人工的——来抓住它？」这个问题的一个并不精确的替代指标。</p>

    <p>问出这个问题，也改变了审查<em>发生的时间</em>。以前，审查是放在最后的一道关卡：写代码、开 PR、等同事来看。</p>

    <p>现在，审查是持续进行的，更像是你写作时的拼写检查。大多数变更在被要求人来看之前，已经至少被 Claude 审过、修过一遍。这拦住了大部分常规错误，也把团队注意力留给了更高层的问题与更细致的审视。</p>

    <h2 id="scarce">稀缺资源：人的时间与注意力</h2>

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

    <p>人的时间涵盖审查成本的两端：人们花在审查上的时间，以及人们花在修复烂代码造成的破坏上的时间。如果削减了前者，时间却在后者身上冒出来，那就没有意义。</p>

    <p>因此我们的目标是：Claude Code 各仓库（CLI、桌面端与 Web 应用、移动端应用）中的绝大多数 pull request，在最多只有一个人参与的情况下完成合并——这个人可以是作者本人——同时回滚率和事故率不上升。为了衡量人的时间，我们跟踪每个 PR 的人工批准次数（中位数与 95 分位）、人工评论数量，以及一个 PR 等待某个人所花的时间。回滚率和事故率则是护栏：如果它们上升，我们就并没有节省人的时间，只是把时间挪了个位置。</p>

    <h2 id="life">一次变更的一生</h2>

    <p>一次典型的变更，从工程师的笔记本电脑（或 Slack 里的一个 Claude 会话）出发，会经过多层代码审查。</p>

    <figure class="art" role="group" aria-label="图 A：一次变更的一生">
      <div class="fig-head">
        <div class="fig-title">一次变更的一生</div>
        <div class="fig-sub">一次典型的 Claude Code 变更，从作者的机器到用户</div>
      </div>
      <div class="fig-draw" role="img" aria-label="从提交 PR 之前到合并之后的流程图：验证、必需的 CI、Claude 代码审查、安全审查、按风险分级批准、合并队列、灰度发布，以及观察、前向修复或回滚。大部分环节是自动化的，只有批准由人决定，并按风险分级。">
<svg class="fig-svg" aria-hidden="true" focusable="false" id="code-review-life-of-a-change" width="651" height="522" viewBox="2 0 651 522" xmlns="http://www.w3.org/2000/svg"><style>#code-review-life-of-a-change text{font-family:'Anthropic Sans','Anthropic Sans Fallback','Anthropic Sans Fallback Roboto',system-ui,'PingFang SC','Hiragino Sans GB','Noto Sans SC',sans-serif}#code-review-life-of-a-change .mono{font-family:'Anthropic Mono','Anthropic Mono Fallback',ui-monospace,'SF Mono',Menlo,monospace}</style>
<path d="M11 33.5 L11 460.5" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--line)"/>
<circle cx="11" cy="33.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<circle cx="11" cy="33.5" r="6.5" fill="none" stroke-width="1" style="stroke:var(--viz-muted)"/>
<path d="M8 33.5 L10.2 35.8 L14.2 31.3" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<circle cx="11" cy="114.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<circle cx="11" cy="114.5" r="6.5" fill="none" stroke-width="1" style="stroke:var(--viz-muted)"/>
<path d="M8 114.5 L10.2 116.8 L14.2 112.3" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<circle cx="11" cy="161.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<circle cx="11" cy="161.5" r="6.5" fill="none" stroke-width="1" style="stroke:var(--viz-muted)"/>
<path d="M8 161.5 L10.2 163.8 L14.2 159.3" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<circle cx="11" cy="208.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<circle cx="11" cy="208.5" r="6.5" fill="none" stroke-width="1" style="stroke:var(--viz-muted)"/>
<path d="M8 208.5 L10.2 210.8 L14.2 206.3" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<circle cx="11" cy="255.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<polygon points="11,248.5 18,255.5 11,262.5 4,255.5" style="fill:var(--fig-focal)"/>
<circle cx="11" cy="336.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<circle cx="11" cy="336.5" r="6.5" fill="none" stroke-width="1" style="stroke:var(--viz-muted)"/>
<path d="M8 336.5 L10.2 338.8 L14.2 334.3" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<circle cx="11" cy="400.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<circle cx="11" cy="400.5" r="6.5" fill="none" stroke-width="1" style="stroke:var(--viz-muted)"/>
<path d="M8 400.5 L10.2 402.8 L14.2 398.3" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<circle cx="11" cy="430.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<circle cx="11" cy="430.5" r="6.5" fill="none" stroke-width="1" style="stroke:var(--viz-muted)"/>
<path d="M8 430.5 L10.2 432.8 L14.2 428.3" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<circle cx="11" cy="460.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<path d="M16.6 462.5 A6 6 0 1 1 14.4 455.5" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<path d="M11.6 453.9 L14.6 455.5 L13.6 458.7" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<text x="32" y="14" font-size="11" font-weight="500" class="mono" style="fill:var(--ink-2)">提交 PR 之前</text>
<text x="32" y="38" font-size="14" font-weight="500" style="fill:var(--ink)">先验证工作本身</text>
<text x="277" y="38" font-size="13" style="fill:var(--ink-2)">Claude 跑测试、构建和 linter；另一个 agent</text>
<text x="277" y="55" font-size="13" style="fill:var(--ink-2)">用全新上下文审查这份 diff</text>
<text x="32" y="95" font-size="11" font-weight="500" class="mono" style="fill:var(--ink-2)">每个 PR 上</text>
<text x="32" y="119" font-size="14" font-weight="500" style="fill:var(--ink)">必需的 CI</text>
<text x="277" y="119" font-size="13" style="fill:var(--ink-2)">lint、类型检查、各平台测试、构建与打包。变红</text>
<text x="277" y="136" font-size="13" style="fill:var(--ink-2)">就不允许合并</text>
<text x="32" y="166" font-size="14" font-weight="500" style="fill:var(--ink)">Claude 代码审查</text>
<text x="277" y="166" font-size="13" style="fill:var(--ink-2)">多个 agent 并行找 bug，验证者投票，</text>
<text x="277" y="183" font-size="13" style="fill:var(--ink-2)">幸存者按严重程度排序</text>
<text x="32" y="213" font-size="14" font-weight="500" style="fill:var(--ink)">安全审查</text>
<text x="277" y="213" font-size="13" style="fill:var(--ink-2)">独立的一遍。任一审查还有未关闭的发现，</text>
<text x="277" y="230" font-size="13" style="fill:var(--ink-2)">都会阻止合并</text>
<text x="32" y="260" font-size="14" font-weight="500" style="fill:var(--ink)">按风险分级批准</text>
<text x="277" y="260" font-size="13" style="fill:var(--ink-2)">分类器决定层级：零人、一人或两人。有些路径</text>
<text x="277" y="277" font-size="13" style="fill:var(--ink-2)">永远需要人</text>
<text x="32" y="317" font-size="11" font-weight="500" class="mono" style="fill:var(--ink-2)">合并</text>
<text x="32" y="341" font-size="14" font-weight="500" style="fill:var(--ink)">合并队列</text>
<text x="277" y="341" font-size="13" style="fill:var(--ink-2)">在最新 main 上应用变更，再重跑全部检查</text>
<text x="32" y="381" font-size="11" font-weight="500" class="mono" style="fill:var(--ink-2)">合并之后</text>
<text x="32" y="405" font-size="14" font-weight="500" style="fill:var(--ink)">放在开关后，内部先用</text>
<text x="277" y="405" font-size="13" style="fill:var(--ink-2)">内部员工先跑，然后再到所有用户</text>
<text x="32" y="435" font-size="14" font-weight="500" style="fill:var(--ink)">灰度发布</text>
<text x="277" y="435" font-size="13" style="fill:var(--ink-2)">先走 latest 通道，stable 随后跟进</text>
<text x="32" y="465" font-size="14" font-weight="500" style="fill:var(--ink)">观察、前向修复或回滚</text>
<text x="277" y="465" font-size="13" style="fill:var(--ink-2)">作者与 on-call 盯着；开关关得快，</text>
<text x="277" y="482" font-size="13" style="fill:var(--ink-2)">坏掉的构建直接回滚</text>
<circle cx="11" cy="511.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<circle cx="11" cy="511.5" r="6.5" fill="none" stroke-width="1" style="stroke:var(--viz-muted)"/>
<path d="M8 511.5 L10.2 513.8 L14.2 509.3" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<text x="26" y="516" font-size="13" style="fill:var(--ink-2)">自动化</text>
<circle cx="122.66593017578126" cy="511.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<polygon points="122.66593017578126,504.5 129.66593017578126,511.5 122.66593017578126,518.5 115.66593017578126,511.5" style="fill:var(--fig-focal)"/>
<text x="137.66593017578126" y="516" font-size="13" style="fill:var(--ink-2)">由人决定，按风险分级</text>
<circle cx="355.21875244140625" cy="511.5" r="7" stroke-width="2" style="fill:var(--bg);stroke:var(--bg)"/>
<path d="M360.8187524414063 513.5 A6 6 0 1 1 358.61875244140623 506.5" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<path d="M355.8187524414063 504.9 L358.8187524414063 506.5 L357.8187524414063 509.7" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-m1)"/>
<text x="370.21875244140625" y="516" font-size="13" style="fill:var(--ink-2)">恢复</text>
</svg>
      </div>
      <p class="fig-hint">左右滑动，查看完整图表 →</p>
      <figcaption><b>图 A</b>一次典型的 Claude Code 变更，从作者的机器到用户。大部分路径是自动化的；人的注意力只去往风险所在之处。</figcaption>
    </figure>

    <h2 id="layers">逐层拆解</h2>

    <figure class="art" role="group" aria-label="图 B：逐层设防，而非一道关卡">
      <div class="fig-head">
        <div class="fig-title">逐层设防，而非一道关卡</div>
        <div class="fig-sub">六层检查，最下面压着回滚</div>
      </div>
      <div class="fig-draw" role="img" aria-label="漏斗图：问题依次经过验证、必需的 CI、AI 审查、安全审查、人工批准、合并队列与开关，最后落到快速回滚。">
<svg class="fig-svg" aria-hidden="true" focusable="false" id="code-review-layers" width="651" height="369" viewBox="0 0 651 369" xmlns="http://www.w3.org/2000/svg"><style>#code-review-layers text{font-family:'Anthropic Sans','Anthropic Sans Fallback','Anthropic Sans Fallback Roboto',system-ui,'PingFang SC','Hiragino Sans GB','Noto Sans SC',sans-serif}#code-review-layers .mono{font-family:'Anthropic Mono','Anthropic Mono Fallback',ui-monospace,'SF Mono',Menlo,monospace}</style>
<text x="40" y="14" font-size="13" style="fill:var(--ink-2)">问题进入</text>
<path d="M188 22 L188 343" fill="none" stroke-width="1" stroke-dasharray="3 3" style="stroke:var(--fig-focal)"/>
<path d="M78.8 22 L78.8 54" fill="none" stroke-width="1" stroke-dasharray="3 3" opacity=".55" style="stroke:var(--fig-focal)"/>
<path d="M297.2 22 L297.2 54" fill="none" stroke-width="1" stroke-dasharray="3 3" opacity=".55" style="stroke:var(--fig-focal)"/>
<path d="M110 65.5 L110 104" fill="none" stroke-width="1" stroke-dasharray="3 3" opacity=".55" style="stroke:var(--fig-focal)"/>
<path d="M271.2 115.5 L271.2 154" fill="none" stroke-width="1" stroke-dasharray="3 3" opacity=".55" style="stroke:var(--fig-focal)"/>
<path d="M136 165.5 L136 204" fill="none" stroke-width="1" stroke-dasharray="3 3" opacity=".55" style="stroke:var(--fig-focal)"/>
<path d="M253 215.5 L253 254" fill="none" stroke-width="1" stroke-dasharray="3 3" opacity=".55" style="stroke:var(--fig-focal)"/>
<path d="M99.6 265.5 L99.6 304" fill="none" stroke-width="1" stroke-dasharray="3 3" opacity=".55" style="stroke:var(--fig-focal)"/>
<polygon points="40,72 300,72 336,50 76,50" stroke-width="1" style="fill:var(--line-soft);stroke:var(--line)"/>
<ellipse cx="188" cy="61" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<ellipse cx="110" cy="61" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<text x="358" y="65.5" font-size="13" style="fill:var(--ink)">验证</text>
<polygon points="40,122 300,122 336,100 76,100" stroke-width="1" style="fill:var(--line-soft);stroke:var(--line)"/>
<ellipse cx="188" cy="111" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<ellipse cx="271.2" cy="111" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<text x="358" y="115.5" font-size="13" style="fill:var(--ink)">必需的 CI</text>
<polygon points="40,172 300,172 336,150 76,150" stroke-width="1" style="fill:var(--line-soft);stroke:var(--line)"/>
<ellipse cx="188" cy="161" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<ellipse cx="136" cy="161" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<text x="358" y="165.5" font-size="13" style="fill:var(--ink)">AI 审查</text>
<polygon points="40,222 300,222 336,200 76,200" stroke-width="1" style="fill:var(--line-soft);stroke:var(--line)"/>
<ellipse cx="188" cy="211" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<ellipse cx="253" cy="211" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<text x="358" y="215.5" font-size="13" style="fill:var(--ink)">安全审查</text>
<polygon points="40,272 300,272 336,250 76,250" stroke-width="1" style="fill:var(--line-soft);stroke:var(--line)"/>
<ellipse cx="188" cy="261" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<ellipse cx="99.6" cy="261" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<text x="358" y="265.5" font-size="13" style="fill:var(--ink)">人工批准</text>
<polygon points="40,322 300,322 336,300 76,300" stroke-width="1" style="fill:var(--line-soft);stroke:var(--line)"/>
<ellipse cx="188" cy="311" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<ellipse cx="281.6" cy="311" rx="14" ry="4.5" stroke-width="1" style="fill:var(--bg);stroke:var(--line)"/>
<text x="358" y="315.5" font-size="13" style="fill:var(--ink)">合并队列 + 开关</text>
<path d="M188 56.5 L188 315.5" fill="none" stroke-width="1" stroke-dasharray="3 3" style="stroke:var(--fig-focal)"/>
<circle cx="78.8" cy="59" r="4.5" stroke-width="2" style="fill:var(--fig-focal);stroke:var(--bg)"/>
<circle cx="297.2" cy="59" r="4.5" stroke-width="2" style="fill:var(--fig-focal);stroke:var(--bg)"/>
<circle cx="110" cy="109" r="4.5" stroke-width="2" style="fill:var(--fig-focal);stroke:var(--bg)"/>
<circle cx="271.2" cy="159" r="4.5" stroke-width="2" style="fill:var(--fig-focal);stroke:var(--bg)"/>
<circle cx="136" cy="209" r="4.5" stroke-width="2" style="fill:var(--fig-focal);stroke:var(--bg)"/>
<circle cx="253" cy="259" r="4.5" stroke-width="2" style="fill:var(--fig-focal);stroke:var(--bg)"/>
<circle cx="99.6" cy="309" r="4.5" stroke-width="2" style="fill:var(--fig-focal);stroke:var(--bg)"/>
<path d="M144 341 Q188 369 232 341" fill="none" stroke-width="1" style="stroke:var(--fig-focal)"/>
<circle cx="188" cy="351" r="5.5" stroke-width="2" style="fill:var(--fig-focal);stroke:var(--bg)"/>
<text x="358" y="355.5" font-size="13" font-weight="500" style="fill:var(--fig-focal)">快速回滚</text>
</svg>
      </div>
      <p class="fig-hint">左右滑动，查看完整图表 →</p>
      <figcaption><b>图 B</b>六层检查，最下面是回滚。每一层都能抓住一些问题、漏掉另一些。很少有人能穿过全部六层，剩下的交给回滚。</figcaption>
    </figure>

    <h3><span class="n">1</span>在别人看之前，先验证工作本身</h3>

    <p>如今大多数审查发生在 PR 存在之前。我们会给 Claude 一些它可以运行的东西——比如测试、构建，或一张可与设计稿对比的截图——并要求它展示输出，而不是只说「做完了」。随后，一个 subagent 会以全新的上下文审查这份 diff。这两个习惯都来自我们的<a href="https://code.claude.com/docs/en/best-practices">最佳实践指南</a>。</p>

    <p>在开 PR 之前，我们中很多人还会跑几个内部 skill：一个专门找 bug，一个负责简化 diff，还有一个端到端地检查行为。UI 变更还会对照我们的设计规范再检查一遍。</p>

    <p>Claude Code 的创造者 Boris Cherny 曾说，给 Claude 一种验证自己工作的方式，<a href="https://x.com/bcherny/status/2007179861115511237">大概是最重要的一件事</a>。他估计这能让最终结果好上两到三倍，并写道：「验证在每个领域长得都不一样。它可能只是跑一条 bash 命令，或者跑一套测试，或者在浏览器或手机模拟器里把应用试一遍。」</p>

    <p><strong>当自动审查者反复指出同一个问题时，我们会从源头修掉它</strong>：改 skill、加一条 lint 规则，或者更新这类指导性文件：</p>

    <ul>
      <li><code>CLAUDE.md</code> 告诉 Claude 这个项目是什么：技术栈、约定、目标与非目标。每次审查都会加载它，所以在大型代码库里我们保持它简短，并从那里指向其他文档。审查者也会标出违反其中约定的代码，以及那些让其中某句话过时的变更。</li>
      <li><code>REVIEW.md</code> 由代码所有者编写，告诉审查者在他们负责的那部分代码里什么才是重要的。我们会单独检查审查者是否真的遵循了它。写得好的都很短。它们会说明在那个仓库里失败长什么样（对文档来说是「它在说谎」，对配置来说是「它把流量路由错了」），限制吹毛求疵的数量，列出需要跳过的路径，并要求以文件和行号作为证据，而不是凭名字猜。它们还会要求审查结论以数字开头，并在没有需要修的东西时直说。</li>
    </ul>

    <h3><span class="n">2</span>每个 pull request 上的 AI 审查</h3>

    <p>在有人看之前，每个 pull request 都会先得到一次 Claude 审查。它把 diff 放在整个仓库和这个 PR 声明的意图之下来读，并追问：这个变更是解决了它自己的问题，还是契合系统，又或者破坏了别的东西？</p>

    <p>在 bug 修复上，它会去找根因。公开版本 <a href="https://code.claude.com/docs/en/code-review">Code Review for Claude Code</a> 现已在 Team 和 Enterprise 套餐上正式可用，而在本地跑 <code>/code-review</code> 会得到更轻量的版本。</p>

    <p>在我们<a href="https://claude.com/resources/articles/code-review">推出研究预览版</a>时，获得实质性审查意见的内部 PR 占比从 16% 上升到了 54%。超过 1,000 行的 PR 大多能被发现问题，而大多数小 PR 会干净通过。管理员可以为每个仓库设置为 Quick、Standard 或 Deep。在「漏掉一个 bug 的代价高于多花的审查时间」的地方，Deep 是值得的。</p>

    <p><strong>在人来审之前，发现的问题会先由其他 agent 验证。</strong>多个 agent 同时找 bug。随后另一组 agent 对每条发现投票，只有足够多的 agent 认为它是真的，它才会被发布出来。只涉及风格、诊断信息或清理工作的发现会被丢弃。我们在自己的 PR 上对这些过滤器做过 A/B 测试，才决定保留它们。剩下的以按严重程度排序的行内评论呈现。每条都标注为 Important、Nit 或 Pre-existing，这样不是这个 PR 引入的 bug，看起来就不会像是作者的责任。</p>

    <p><strong>每条发现都必须被回应。</strong>要么修掉它，要么带着理由把讨论串标记为已解决。通常会有 agent 在夜里接手修复，于是作者第二天回来时，PR 已经又过了一轮。</p>

    <h3><span class="n">3</span>能够阻止合并的安全审查</h3>

    <p>安全有它自己的自动审查。只要 bug 审查或安全审查还有任何未关闭的发现，PR 就不能合并。你要么修掉它，要么用一句简短的理由关掉讨论串——这也是清除误报的方式。</p>

    <p>如果后续的提交修复了问题，那条发现会自动清除。你也可以用开源的安全审查 GitHub Action 或 <code>/security-review</code> 命令搭建同样的机制。搭建方式见 <a href="https://support.claude.com/en/articles/11932705-automated-security-reviews-in-claude-code">Claude Code 中的自动化安全审查</a>。</p>

    <h3><span class="n">4</span>能够阻止合并的自动化检查</h3>

    <p>就是那些常规项：lint、类型检查、各平台上的测试，以及构建。（我们写过一篇关于如何为这种体量扩展 CI 的文章，见<a href="https://claude.com/resources/articles/agentic-coding-is-straining-ci-heres-how-we-scaled-test-impact-analysis-at-anthropic">这里</a>。）一个典型的 CLI pull request 在有人看之前可能会跑几十项检查。PR 模板会问这个变更是怎么测的、怎么撤销。Claude 可以把它填好，但提出这个变更需求的人要对它签字确认。</p>

    <h3><span class="n">5</span>人工批准，按风险分级</h3>

    <p>一次文档修复和一次自动更新器的改动，风险并不相同，所以它们得到的审查也不同。</p>

    <div class="table-wrap">
      <table>
        <thead>
          <tr><th>风险</th><th>示例</th><th>人工批准</th></tr>
        </thead>
        <tbody>
          <tr><td>低</td><td>文档、文案、仅内部使用的功能、开关后面的增量变更</td><td>自动审查与安全检查全绿后，无需人工批准</td></tr>
          <tr><td>中</td><td>普通的产品变更，以及对敏感区域的变更，例如缓存、自动更新器或系统提示词</td><td>一人</td></tr>
          <tr><td>高</td><td>风险最高的代码，以及一小批始终需要两人的路径</td><td>两人</td></tr>
        </tbody>
      </table>
    </div>

    <p><strong>分级由变更决定，而不是由作者决定。</strong>一个分类器读取变更内容，判定它的风险层级。第二个分类器则追问：即使这个层级本不需要，这次变更是否仍需另一个人参与。有些路径永远需要人，包括所有者列为敏感的任何东西，以及任何 CI workflow 文件。</p>

    <p><strong>只有受信任的作者才能走更轻的路径。</strong>只有在仓库受信任名单上的作者才能走更轻的路径，而名单上主要是拥有这个仓库的团队。其他人都需要第二个人来批准。agent 的批准也永远不能算作人的批准。自动审查可以放行一个低风险变更，但它不能填补一个需要人来的位置。</p>

    <p><strong>谁提出的变更，谁就要为它负责。</strong>在一个由 agent 写出的 PR 上，指挥这个 agent 的人算作必需的审查者之一，就如同这些代码是他亲手敲的一样。</p>

    <p><strong>一次批准只覆盖一个特定的 commit。</strong>如果之后落入了系统无法担保的新 commit——比如 agent 推送的，或某人手工解决的合并——批准就必须重新给出。</p>

    <h3><span class="n">6</span>合并队列、开关与灰度发布</h3>

    <p>合并队列会在应用了该变更的基础上，对 main 重新跑所有检查，所以两个各自通过的 PR 不可能组合出一个坏掉的 main。新行为通常会先放在开关后面，先发给 Anthropic 内部员工；我们自己的重度使用，往往能在用户之前暴露出回归。公开发布先走 <code>latest</code> 通道，<code>stable</code> 通道随后跟进。</p>

    <h3><span class="n">7</span>快速发现，快速回滚</h3>

    <p>总还会有一些错误漏过去，所以最后一层是尽快抓住它们。开关可以不用部署就能关掉。Claude Code 有每周轮值的 on-call，事故复盘会追问本应由哪一层抓住这个问题。</p>

    <p>发现一次糟糕的合并，仍然依赖作者、on-call，以及回滚和事故看板。我们目前还没有能在这些指标变动时收紧审批的熔断机制；在更多代码转向「零人工合并」之前，工程师们希望先有它。</p>

    <p>一位同事说过，慢下来确实会降低出错率，但那主要是因为你在所有事情上都做得更少。我们更愿意在「更快地发现并修复问题」上变得更强。</p>

    <h2 id="reviewer">我们如何让审查者变得更好</h2>

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

    <p><strong>任何事故现在都是一份 eval。</strong>团队收集了过去引发事故的 PR，连同它们的 fix-forward 和回滚，并请熟悉那部分代码的工程师确认每一个 bug 以及它所在的位置。审查者每天都会在这个集合上跑一遍。</p>

    <figure class="art" role="group" aria-label="图 C：事故就是我们的 eval">
      <div class="fig-head">
        <div class="fig-title">事故就是我们的 eval</div>
        <div class="fig-sub">由历史事故和回滚构建，每天运行</div>
      </div>
      <div class="fig-draw" role="img" aria-label="循环图：一次事故或回滚经过专家核对后加入用例集；每次改动审查者（提示词、工具或模型）都要在每日 eval 中对全量用例打分，分数上涨则保留，否则丢弃，然后进入下一次改动。">
<svg class="fig-svg" aria-hidden="true" focusable="false" id="code-review-incident-eval" width="651" height="196" viewBox="28 26 651 196" xmlns="http://www.w3.org/2000/svg"><style>#code-review-incident-eval text{font-family:'Anthropic Sans','Anthropic Sans Fallback','Anthropic Sans Fallback Roboto',system-ui,'PingFang SC','Hiragino Sans GB','Noto Sans SC',sans-serif}#code-review-incident-eval .mono{font-family:'Anthropic Mono','Anthropic Mono Fallback',ui-monospace,'SF Mono',Menlo,monospace}</style>
<text x="54" y="44.5" font-size="14" font-weight="500" style="fill:var(--ink)">事故或回滚</text>
<text x="54" y="62" font-size="12.5" style="fill:var(--viz-muted)">一次搞坏东西的变更</text>
<text x="271" y="44.5" font-size="14" font-weight="500" style="fill:var(--ink)">专家核对的用例</text>
<text x="271" y="62" font-size="12.5" style="fill:var(--viz-muted)">专家确认 bug 存在</text>
<text x="488" y="44.5" font-size="14" font-weight="500" style="fill:var(--ink)">加入用例集</text>
<text x="488" y="62" font-size="12.5" style="fill:var(--viz-muted)">每次事故增加一例</text>
<text x="54" y="154.5" font-size="14" font-weight="500" style="fill:var(--ink)">改动审查者</text>
<text x="54" y="172" font-size="12.5" style="fill:var(--viz-muted)">提示词、工具或模型</text>
<text x="271" y="154.5" font-size="14" font-weight="500" style="fill:var(--ink)">每日 eval</text>
<text x="271" y="172" font-size="12.5" style="fill:var(--viz-muted)">对每条用例打分</text>
<text x="488" y="154.5" font-size="14" font-weight="500" style="fill:var(--ink)">分数涨了吗？</text>
<text x="488" y="172" font-size="12.5" style="fill:var(--viz-muted)">涨了就留，否则丢弃</text>
<path d="M181.3 40 L247 40" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--viz-muted)"/>
<path d="M243 36.5 L247 40 L243 43.5" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-muted)"/>
<path d="M422.6 40 L464 40" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--viz-muted)"/>
<path d="M460 36.5 L464 40 L460 43.5" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-muted)"/>
<path d="M474 50 L474 104 L257 104 L257 140" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--viz-muted)"/>
<path d="M253.5 136 L257 140 L260.5 136" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-muted)"/>
<path d="M203.4 150 L247 150" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--viz-muted)"/>
<path d="M243 146.5 L247 150 L243 153.5" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-muted)"/>
<path d="M368.9 150 L464 150" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--viz-muted)"/>
<path d="M460 146.5 L464 150 L460 153.5" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-muted)"/>
<path d="M474 160 L474 214 L40 214 L40 160" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--viz-muted)"/>
<path d="M36.5 164 L40 160 L43.5 164" fill="none" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--viz-muted)"/>
<text x="257" y="207" text-anchor="middle" font-size="13" style="fill:var(--ink-2)">下一次改动</text>
<circle cx="40" cy="40" r="5.5" stroke-width="2" style="fill:var(--viz-m1);stroke:var(--bg)"/>
<circle cx="257" cy="40" r="5.5" stroke-width="2" style="fill:var(--viz-m1);stroke:var(--bg)"/>
<circle cx="474" cy="40" r="5.5" stroke-width="2" style="fill:var(--viz-m1);stroke:var(--bg)"/>
<circle cx="40" cy="150" r="5.5" stroke-width="2" style="fill:var(--viz-m1);stroke:var(--bg)"/>
<circle cx="257" cy="150" r="5.5" stroke-width="2" style="fill:var(--fig-focal);stroke:var(--bg)"/>
<circle cx="474" cy="150" r="5.5" stroke-width="2" style="fill:var(--viz-m1);stroke:var(--bg)"/>
</svg>
      </div>
      <p class="fig-hint">左右滑动，查看完整图表 →</p>
      <figcaption><b>图 C</b>由历史事故与回滚构建，每天运行。eval 随每次事故增长，审查者的每一次改动都会针对完整集合打分。</figcaption>
    </figure>

    <p><strong>我们通过观察人们如何对待评论来衡量噪声。</strong>如果你不在意误报很多，想多抓 bug 是很容易的，所以我们需要对这一点加以检查。我们会按 PR 上那个人的反应给审查者留下的每条评论分类：他修了它、他说它错了、他说这个行为是有意为之，或者他认为它无关紧要。我们还会把审查者跑在一些我们已知是干净的 PR 上，统计它报告出的、会让人做出有害改动的问题有多频繁。我们写下来的优先级是：先抓真 bug，其次减少审查轮次。</p>

    <p>当作者是 agent 时，噪声的代价更高。一个人可以在几秒内把一个 nit 挥手带过。而 agent 会去修它，这意味着又一次提交和又一轮审查。</p>

    <p><strong>审查必须收敛。</strong>一个 agent 作者和一个 agent 审查者可以让彼此无限忙碌下去。有一个 CLI PR 在一夜之间经历了五十多轮审查与修复，最后是作者叫停了它。</p>

    <p>当我们对审查者反复指出的内容跑 git blame 时，大约 59% 是审查者第一轮就已经存在的代码。审查者很少在发现新 bug，它只是在每一轮里多发现几个旧 bug。</p>

    <p>这是我们自己搞出来的。我们已经习惯默认跑好几轮，因为每一轮都会抓到一点新东西。这在一次高风险变更上也许值得，但不该在每一个 PR 上都如此。团队现在把「过审轮次」和召回率放在一起跟踪，并修改了重审策略：</p>

    <ul>
      <li><strong>轮次上限。</strong>三轮之后，审查者会请作者停下来，重新考虑做法。</li>
      <li><strong>只审 diff 的重审</strong>，在前几轮之后启用。</li>
      <li><strong>标出 bug 的类型</strong>，让作者修掉所有同类实例，而不只是一行。</li>
      <li><strong>不重复</strong>已经发布过的发现。</li>
    </ul>

    <p>审查 agent 还会每晚在既有代码上跑一遍，帮我们在旧 bug 冒到别处之前抓住它们。</p>

    <figure class="art" role="group" aria-label="图 D：能够收敛的审查">
      <div class="fig-head">
        <div class="fig-title">能够收敛的审查</div>
        <div class="fig-sub">收敛性改动前后的内部 pull request</div>
      </div>
      <div class="fig-draw" role="img" aria-label="改动前后的对比：一轮审查加修复后通过的比例从 65% 升到 78%；需要五轮及以上的比例从 8.9% 降到 1.5%；平均过审轮次从 2.08 降到 1.32。">
<svg class="fig-svg" aria-hidden="true" focusable="false" id="code-review-convergence" width="651" height="253" viewBox="0 0 651 253" xmlns="http://www.w3.org/2000/svg"><style>#code-review-convergence text{font-family:'Anthropic Sans','Anthropic Sans Fallback','Anthropic Sans Fallback Roboto',system-ui,'PingFang SC','Hiragino Sans GB','Noto Sans SC',sans-serif}#code-review-convergence .mono{font-family:'Anthropic Mono','Anthropic Mono Fallback',ui-monospace,'SF Mono',Menlo,monospace}</style>
<text x="2" y="14" font-size="13" font-weight="500" style="fill:var(--ink)">一轮审查加修复后通过</text>
<rect x="2" y="26" width="234.0" height="14" style="fill:var(--viz-b9)"/>
<text x="244" y="37" font-size="13" font-weight="400" style="fill:var(--ink-2)">65%</text>
<rect x="2" y="46" width="280.8" height="14" style="fill:var(--fig-focal)"/>
<text x="290.8" y="57" font-size="13" font-weight="500" style="fill:var(--ink)">78%</text>
<path d="M2 76 L647 76" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--line-soft)"/>
<text x="2" y="96" font-size="13" font-weight="500" style="fill:var(--ink)">需要五轮及以上</text>
<rect x="2" y="108" width="32.0" height="14" style="fill:var(--viz-b9)"/>
<text x="42.04" y="119" font-size="13" font-weight="400" style="fill:var(--ink-2)">8.9%</text>
<rect x="2" y="128" width="5.4" height="14" style="fill:var(--fig-focal)"/>
<text x="15.4" y="139" font-size="13" font-weight="500" style="fill:var(--ink)">1.5%</text>
<path d="M2 158 L647 158" fill="none" stroke-width="1" stroke-linejoin="round" style="stroke:var(--line-soft)"/>
<text x="2" y="178" font-size="13" font-weight="500" style="fill:var(--ink)">平均过审轮次</text>
<rect x="2" y="190" width="249.6" height="14" style="fill:var(--viz-b9)"/>
<text x="259.6" y="201" font-size="13" font-weight="400" style="fill:var(--ink-2)">2.08</text>
<rect x="2" y="210" width="158.4" height="14" style="fill:var(--fig-focal)"/>
<text x="168.4" y="221" font-size="13" font-weight="500" style="fill:var(--ink)">1.32</text>
<rect x="2" y="239" width="10" height="10" style="fill:var(--viz-b9)"/>
<text x="18" y="248" font-size="13" style="fill:var(--ink-2)">改动之前</text>
<rect x="84.26945755004883" y="239" width="10" height="10" style="fill:var(--fig-focal)"/>
<text x="100.26945755004883" y="248" font-size="13" style="fill:var(--ink-2)">收敛性改动之后</text>
</svg>
      </div>
      <p class="fig-hint">左右滑动，查看完整图表 →</p>
      <figcaption><b>图 D</b>收敛性改动之后，每个内部 pull request 的审查轮次减少了。</figcaption>
    </figure>

    <h2 id="humans">人类仍然留在环路中的地方</h2>

    <p>把一位资深工程师从深度工作中拉出来，只为抓一个常规错误，代价远高于一次自动审查。团队里的人仍然要对这些事情负责：</p>

    <ul>
      <li><strong>决定是否要做。</strong>方向、品味与范围。有一个团队在宣布自己的审查瓶颈已解决之后，仍然对来自团队之外的变更保留人工审查，好让有人问一句「我们真的该做这件事吗？」</li>
      <li><strong>审查高风险代码</strong>，例如计费、自动更新器和 CI workflow。</li>
      <li><strong>审查架构</strong>，即那些会为他人树立范式的设计。</li>
      <li><strong>阅读证据。</strong>测试输出、跑过的命令及其返回、一张截图、审查者的发现。如果你说不出这些检查会漏掉什么，那就去要一次真正的审查，而不是草草批准。</li>
      <li><strong>对结果负责。</strong>每一个上线的变更都有人负责，无论代码是谁敲的。</li>
    </ul>

    <h2 id="context">在团队中共享上下文</h2>

    <p>代码审查的第二项职责，一直是传播对代码库的理解。读一位同事的 diff，是你了解发生了什么变化、各部分如何拼在一起的方式。如果大部分阅读都由机器完成，人们又怎么学习代码？</p>

    <p>我们没有为此设立正式流程。团队里没有人脑中保留着 Claude Code 的完整心智模型，我们也认为没这个必要。当有人想理解代码库的某一部分时，他们会请 Claude 针对那一个切片写一份讲解。几分钟就能出来，而且反映的是当下的代码。</p>

    <figure class="art" role="group" aria-label="配图：如果大部分阅读都交给机器，人们怎么学代码？">
      <div class="fig-head">
        <div class="fig-title">如果大部分阅读都交给机器，人们怎么学代码？</div>
        <div class="fig-sub">从代码库里的一个切片，到一份讲解，或者一个能教会你的小产物</div>
      </div>
      <div class="fig-draw" role="img" aria-label="代码库中只有一小块是你想理解的（托管设置），没人脑中装着完整的心智模型。就这一个切片去问 Claude，它会写出一份几分钟完成、反映当下代码的讲解；更进一步，它还能做一个小产物，把代码可视化，按你习惯的方式讲给你听。逐行读琐碎的 diff 几乎学不到东西，而为你要改的模块要一份讲解能教你很多。">
        <svg class="fig-svg" viewBox="0 0 651 380" width="651" height="380" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
          <style>#fig-explain text{font-family:'PingFang SC','Hiragino Sans GB','Noto Sans SC',system-ui,sans-serif}</style>
          <g id="fig-explain">
            <!-- codebase card -->
            <rect x="2" y="30" width="284" height="220" rx="10" fill="var(--bg-raised)" stroke="var(--line-soft)"/>
            <text x="18" y="56" font-size="14" font-weight="600" fill="var(--ink)">代码库</text>

            <g fill="var(--line)">
              <rect x="26" y="74" width="22" height="22" rx="3"/><rect x="56" y="74" width="22" height="22" rx="3"/><rect x="86" y="74" width="22" height="22" rx="3"/><rect x="116" y="74" width="22" height="22" rx="3"/><rect x="146" y="74" width="22" height="22" rx="3"/><rect x="176" y="74" width="22" height="22" rx="3"/><rect x="206" y="74" width="22" height="22" rx="3"/>
              <rect x="26" y="104" width="22" height="22" rx="3"/><rect x="56" y="104" width="22" height="22" rx="3"/><rect x="146" y="104" width="22" height="22" rx="3"/><rect x="176" y="104" width="22" height="22" rx="3"/><rect x="206" y="104" width="22" height="22" rx="3"/>
              <rect x="26" y="134" width="22" height="22" rx="3"/><rect x="56" y="134" width="22" height="22" rx="3"/><rect x="146" y="134" width="22" height="22" rx="3"/><rect x="176" y="134" width="22" height="22" rx="3"/><rect x="206" y="134" width="22" height="22" rx="3"/>
              <rect x="26" y="164" width="22" height="22" rx="3"/><rect x="56" y="164" width="22" height="22" rx="3"/><rect x="86" y="164" width="22" height="22" rx="3"/><rect x="116" y="164" width="22" height="22" rx="3"/><rect x="146" y="164" width="22" height="22" rx="3"/><rect x="176" y="164" width="22" height="22" rx="3"/><rect x="206" y="164" width="22" height="22" rx="3"/>
            </g>
            <g fill="var(--fig-focal)">
              <rect x="86" y="104" width="22" height="22" rx="3"/><rect x="116" y="104" width="22" height="22" rx="3"/>
              <rect x="86" y="134" width="22" height="22" rx="3"/><rect x="116" y="134" width="22" height="22" rx="3"/>
            </g>

            <rect x="26" y="202" width="9" height="9" rx="2" fill="var(--fig-focal)"/>
            <text x="42" y="211" font-size="12" fill="var(--ink-2)">托管设置——你想理解的那部分</text>
            <text x="26" y="234" font-size="11.5" fill="var(--ink-3)">没人脑中装着它完整的心智模型</text>

            <!-- connector -->
            <path d="M288 140 L296 140 M296 97 L296 200 M296 97 L303 97 M296 200 L303 200" fill="none" stroke-width="1.2" stroke-linejoin="round" style="stroke:var(--ink-3)"/>
            <path d="M300 93.5 L303.5 97 L300 100.5" fill="none" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>
            <path d="M300 196.5 L303.5 200 L300 203.5" fill="none" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round" style="stroke:var(--ink-3)"/>

            <!-- right column -->
            <text x="314" y="46" font-size="12" fill="var(--ink-3)">就这一个切片，问 Claude</text>

            <rect x="306" y="58" width="343" height="78" rx="10" fill="var(--bg-raised)" stroke="var(--line-soft)"/>
            <text x="324" y="86" font-size="14" font-weight="600" fill="var(--ink)">Claude 写一份讲解</text>
            <text x="324" y="110" font-size="12" fill="var(--ink-2)">几分钟完成，反映当下的代码</text>

            <rect x="306" y="150" width="343" height="100" rx="10" fill="var(--bg-panel)" stroke="var(--fig-focal)" stroke-opacity=".55"/>
            <text x="324" y="174" font-size="10" font-weight="600" fill="var(--fig-focal)">更进一步</text>
            <text x="324" y="196" font-size="14" font-weight="600" fill="var(--ink)">Claude 做一个小产物</text>
            <text x="324" y="220" font-size="12" fill="var(--ink-2)">把代码画出来，按你习惯的方式</text>
            <text x="324" y="238" font-size="12" fill="var(--ink-2)">讲给你听</text>

            <!-- bottom strips -->
            <rect x="2" y="276" width="316" height="92" rx="10" fill="none" stroke="var(--line-soft)"/>
            <text x="20" y="306" font-size="13" font-weight="600" fill="var(--ink)">逐行读琐碎的 diff</text>
            <text x="20" y="332" font-size="12" fill="var(--ink-2)">一处一行的文案改动，</text>
            <text x="20" y="352" font-size="12" fill="var(--ink-2)">几乎什么都教不了你</text>

            <rect x="330" y="276" width="319" height="92" rx="10" fill="var(--ink)"/>
            <text x="348" y="306" font-size="13" font-weight="600" fill="var(--bg)">为你要改的模块要一份讲解</text>
            <text x="348" y="332" font-size="12" fill="var(--bg)" fill-opacity=".72">那一份讲解，能教你很多</text>
          </g>
        </svg>
      </div>
      <figcaption><b>配图</b>读琐碎的 diff 学不到东西；为你要改的那个模块要一份讲解，那能教你很多。</figcaption>
    </figure>

    <p>我们中的一些人还会更进一步，让 Claude 做一个小产物，把那部分代码可视化，并按这个人喜欢的学习方式来讲解。</p>

    <p>如果目标是把人的注意力花好，这比逐行阅读琐碎的 diff 强得多。一处一行的文案改动几乎什么都教不了你。为你即将改动的模块写一份讲解，能教你很多。我们「默认在公开 Slack 频道里工作」的文化也有帮助，因为别人在构建东西时，你能看见他们在构建什么。</p>

    <h2 id="ownership">「你合入，你负责」</h2>

    <p>如果你合入了一个小或中等规模的变更，而事情仍然出了问题，团队规范是通常由你来修。有几件事让它不至于变得糟糕：</p>

    <ul>
      <li><strong>变更很小</strong>，容易审查、调试和回滚。</li>
      <li><strong>一切都是可逆的。</strong>开关、回滚和分阶段发布能在几分钟内撤销大多数错误。</li>
      <li><strong>影响半径决定门槛。</strong>昂贵的代码会得到更多的人和更多的检查。</li>
      <li><strong>回归暴露得很快</strong>，通过遥测和内部使用。</li>
      <li><strong>这不是追责。</strong>拥有修复意味着你掌握最多上下文；事故复盘看的是系统。</li>
    </ul>

    <p>责任也是共享的。团队大部分人都在<a href="https://claude.com/product/tag">公开频道</a>里工作，任何人都能看到别人在构建什么、接手一个讨论串，或者把一个 Claude 指向某处空白；反馈也来自那些并不拥有这段代码的人。这种开放性是质量能保持在高位、而不必对每一行都设关卡的重要原因。</p>

    <h2 id="direction">当我们改变方向时</h2>

    <p>agent 让开始一件事变得很便宜，所以有些工作不该落地。通常是频道里有人问一句为什么、指出一个更早的决定，或者提出一条更简单的路径，然后作者关闭或重塑这个 PR。</p>

    <p>既然代码写得很快，关掉这个 PR 并不让人觉得是损失。诀窍在于尽早在这个讨论串里提出另一个方向，而不是把它作为一条阻塞性评论，留在一个已经完成的 PR 上。</p>

    <h2 id="adopt">你如何采用这套流程</h2>

    <p>和你的团队一起做这件事所需的大部分组件，今天都已经存在了。</p>

    <h3><span class="n">▸</span>在 pull request 之前</h3>
    <ul>
      <li>给你的 agent 一些它能跑的检查，写在简短的 <code>CLAUDE.md</code> 里，再加上运行格式化和 linter 的 hook。</li>
      <li>在本地跑 <code>/code-review</code> 和 <code>/security-review</code>，或者跑一次全新上下文的 subagent 审查。</li>
      <li>加一个 <code>REVIEW.md</code>，把重复出现的审查意见搬进去。</li>
    </ul>

    <h3><span class="n">▸</span>在每个 pull request 上</h3>
    <ul>
      <li>要求 CI 通过，并在 PR 模板里加上「测试方式」「风险」「如何回滚」。</li>
      <li>先在一个仓库上加 AI 审查者（Code Review for Claude Code，或 Claude Code GitHub Action），花几周时间调整 <code>REVIEW.md</code>，然后再铺开。</li>
      <li>加上自动化安全审查，并要求合并前所有审查讨论串都已解决。</li>
      <li>让审查投入与风险匹配：常规变更快速过一遍，漏掉 bug 代价高昂的地方做深度审查（Code Review 中的 Quick、Standard 或 Deep）。</li>
      <li>给审查轮次设上限；前几轮之后，只重审新的 diff。</li>
    </ul>

    <h3><span class="n">▸</span>批准</h3>
    <ul>
      <li>把风险层级写下来，由变更而非作者决定；从严开始，再根据证据放宽。</li>
      <li>把更轻的批准路径留给你信任的作者。</li>
      <li>使用 CODEOWNERS 和分支保护，让认证、计费、CI 与发布 workflow、安装器始终需要人。</li>
      <li>永远不要让 bot 的批准替代一个必需的人；有新 commit 时撤销已有批准。</li>
    </ul>

    <h3><span class="n">▸</span>合并与之后</h3>
    <ul>
      <li>使用合并队列、功能开关、内部先用、分阶段发布和一步回滚。</li>
      <li>把事故和回滚变成一套 eval，每当审查者的提示词、工具或模型变化时就重新打分。</li>
      <li>衡量人的时间，并以回滚率和事故率作为护栏。</li>
    </ul>

    <h2 id="where">这件事将走向何处</h2>

    <p>Code Review 的<a href="https://claude.com/resources/articles/code-review">公告</a>说，批准一个 pull request 仍然由人决定；产品发布的是发现，而不是批准。我们自己的仓库走得更远，让所有者决定哪些低风险变更可以仅凭自动化检查就合并，而这条线还在不断移动。</p>

    <p>当一次审查漏掉了什么，我们会问：模型当时手上缺了什么？然后把修复写进 <code>CLAUDE.md</code>、一个 skill 或一项新检查。目标没有变：每个变更花更少的人类时间，同时回滚不比以前更多。</p>

    <p>如果你的团队让 agent 开 pull request，我们很想听听你们自动化了哪些检查、哪些仍然需要人，以及你认为我们哪里搞错了。</p>

    <hr>

    <p class="thanks">感谢 Emma Webb、Michael Segner 和 Amie Rotherham 的审阅。</p>

    <section class="related" aria-label="相关文章">
      <h2>相关文章</h2>
      <ul>
        <li><time datetime="2026-10-08">10 月 8 日</time><a href="https://claude.dev/blog/building-effective-agent-automations/">构建有效的 agent 自动化</a><span>9 分钟</span></li>
        <li><time datetime="2026-10-06">10 月 6 日</time><a href="https://claude.dev/blog/claude-code-in-the-cloud/">云端的 Claude Code：云会话实战指南</a><span>18 分钟</span></li>
        <li><time datetime="2026-10-01">10 月 1 日</time><a href="https://claude.dev/blog/getting-started-with-claude-code-mods/">Claude Code mods 入门</a><span>11 分钟</span></li>
        <li><time datetime="2026-09-28">9 月 28 日</time><a href="https://claude.dev/blog/automating-eval-design-and-hillclimbing/">用 Claude 自动化 eval 设计与爬山</a><span>12 分钟</span></li>
        <li><time datetime="2026-09-28">9 月 28 日</time><a href="https://claude.dev/blog/building-with-claude-sonnet-5-5/">用 Claude Sonnet 5.5 构建</a><span>9 分钟</span></li>
      </ul>
    </section>]]></content><author><name>yueningbo</name></author><category term="工程" /><category term="AI 编码" /><category term="工程实践" /><category term="流程设计" /><summary type="html"><![CDATA[我们之前说过，Claude Code 自身约 90% 的代码是 Claude 写的。那么，在我们这样的产出速度下，代码是怎么被审查的？现在还有人在读代码吗？如果真的出了问题，又是谁发现的？ 这篇文章讲的是 Claude Code 团队的代码审查流程，以及它是如何演进的。从 6 月到 9 月，合并进 Claude Code 代码库的 pull request 数量增长了一倍多，而其中不得不回滚的比例下降了超过 40%。 我们并没有取消审查。但我们把大部分「阅读」工作交给了机器，围绕人的时间和注意力做优化，并在「让错误能被快速、轻松地撤销」这件事上投入了大量精力。]]></summary></entry></feed>