一、引言:Agent 已经会自查了,缺的是你脑子里那份清单
原文链接:Building verification loops in Claude Code with skills
来源:Claude Blog / Anthropic · 作者:Delba de Oliveira(Claude Code 团队)
发布时间:2026 年 7 月 22 日
用 Claude Code 久了,大概都会形成一套固定的收尾动作:它说改完了,你不太信,于是起一遍服务、点一遍页面、翻一眼控制台、顺手grep 一下日志里有没有把请求体打出去。检查项每次都一样,做检查的人每次都是你。
这篇文章处理的就是这件事,而且判断很直接——这些步骤不该由你做,它们应该被写下来。
值得注意的是它给出的因果方向:不是"Claude 不会验证,所以你得补",而是"Claude 已经在验证了,只是它验证不到你脑子里的规则"。这个区别决定了你该往哪儿投入精力。
二、什么是验证循环:定义先收窄,边界才清楚
原文对验证循环给了一个很克制的定义:
验证循环是一个重复的周期:AI 代理检查自己的工作——跑测试、跑 linter、跑自定义检查——并在往下走之前把失败的地方修掉。
两个词是关键。一是自己:检查动作发生在代理回话之前,不是回话之后由你来做。二是修掉:只报告不修的,那叫检查,不叫循环。循环的定义要求它能带着失败信号回到起点重跑。
放回 agentic 循环里看,位置就很清楚了:收集上下文 → 采取行动 → 验证结果,验证不通过就带着失败信号回到第一步补上下文。
而这一环的燃料——验证信号——分成两类,处境完全不同:
第一类是代码库里的确定性信号:类型检查器、linter、测试、运行时错误。这类信号 Claude 本来就会主动去观察和接收,你几乎不用做什么。原文只提了一条实践建议:把精确的构建与测试命令写进 CLAUDE.md,别让它去猜。这句话看着朴素,但它避免的是最典型的一类浪费——Claude 用一个错的命令跑了半天,拿到一个和你本地完全不同的结果。
第二类是推断不出来的检查,也就是原文那句话:Whatever Claude can’t infer becomes the steps you take to manually check a feature.(凡是 Claude 推断不出来的,就变成你手动检查一个功能时要走的步骤。)
这一类的典型样貌不是"代码写得对不对",而是"符不符合我们这个项目的规矩":
- 错误日志必须带request ID,而且绝对不能把请求体打进去;
- 任何删列的数据库迁移,必须先有回填步骤。
第二条是原文的 Pro tip,也是全文我认为最有价值的一句提醒:
检查不必是定性的才有资格放进来。“拒绝任何删列却没有回填步骤的迁移"是一条确定性规则,没有哪个通用 linter 会抓它,但项目专属的检查会。
也就是说,判断标准不是"这个检查够不够复杂 / 够不够主观”,而是你是不是一直在用手把它执行。是,它就该被编码成循环。
三、先把内置的六种用完,再考虑自己写
在动手造之前,原文先摆出Claude 已经内置的验证能力。这个顺序不是客套——自己写的每一条检查都要维护,能用现成的就别造。
六种能力可以按"配置成本 / 覆盖范围"分成三层:
会话内(零配置,改完立刻查)
/verifySkill:构建、运行、观察你的改动在应用里的真实表现。原文在最后的落地清单里把它排在第 2 步——写自定义 Skill 之前先拿它试一遍。- 工具链信号(Toolchain):Claude 会主动捕获你提供的任何工具的错误码与告警。配套动作就是上面说的,把命令写进
CLAUDE.md。
仓库内(规则写死在项目里,换人也一样跑)
- 规格校验(Spec validation):一个 Skill,拿仓库里的 Markdown 规格逐条比对每次改动,并尝试修掉违规。这个模式对"有规范文档但没人遵守"的项目特别对症。
- GitHub Actions:定义一个 job,用验证 Skill 调起 Claude,于是你本地跑的那套检查,在每次 push / PR 上原样重放。
团队 / 托管(不依赖谁记得)
- Code Review(research preview):托管的多代理服务,对你启用的仓库自动过一遍 PR。它给的闭环方式有点意思——你可以手动改完再推,也可以直接在那条结论下回复
@claude(前提是已经配好 GitHub Actions),把回路交回给代理。 - Claude Managed Agents 里的评分量规(Rubrics,beta):用一个独立的评分代理按量规校验产出结果,不达标的自动打回返工。这里的关键词是"独立"——生产者和评分者分开,比让同一个代理自己给自己打分更可信。
这三层的递进关系,其实已经预告了后面第六节的主题:验证能力真正的差别,往往不在检查内容,而在它跑在哪一层。
四、怎么写出一条属于你项目的验证循环
原文给了三个入口,对应三种处境。
处境一:老项目,你已经在重复同样的修正。
信号非常明确——每次 Claude 实现一个新功能,你都在做同样的几处小修正。这时第一步不是写 Skill,而是把你每次都在做的事情全部写下来。先做记录,再做抽象。
处境二:新项目,你还没有既成流程。
那就写"最佳实践版本"。原文这里的表述我很喜欢:
用平白的英文写下来,就像你把它交给第一天入职的新队友那样。
这句话是一个非常好的写作标尺。交接给新人的说法,天然具备三个性质:不省略前提、不用只有你懂的黑话、明确说清"做完什么算通过"。这恰好就是一个 Skill 需要的东西。
处境三:你知道要检查什么,但说不清楚。
原文的建议是反过来做:先让 Claude 给出该领域的最佳实践,然后在它的版本上改。因为你的做法大概只在几个具体点上和通用实践不同,而这几处不同,正是你真正要捕获的东西。
这是一个被低估的技巧。从零写规范容易陷入"什么都想写一点",从一份通用稿上删改则会强迫你回答一个更锐利的问题:我们和标准做法到底哪里不一样,为什么?
五、编码成 Skill:一个最小可用的 SKILL.md
最快的路径是装上 skill-creator 插件,让 Claude 反过来采访你:
/skill-creator Create a skill for verifying frontend changes end-to-end. Interview me about my workflow.
也可以手写——往项目的 .claude/skills/ 里丢一个 Markdown 文件即可。原文给的最小样例只有几行 frontmatter 加一段正文:
# .claude/skills/verify-log-hygiene/SKILL.md
---
name: verify-log-hygiene
description: Check that error logs include the request ID and never
include the request body. Use when the diff touches error handling
or logging.
allowed-tools: [Read, Edit, Grep]
---
Read the error-handling paths in the current diff.
For each log call on an error path, confirm it includes the request ID
and does not pass the request body, headers, or any user-supplied payload.
Report each violation with file:line, then fix it: add the request ID
where it's missing and strip the payload from the log call.
这个例子短,但四个部分一个不少,值得逐个拆:
| 部分 | 作用 | 容易写砸的地方 |
|---|---|---|
name |
标识 | 无 |
description |
路由信号,决定它什么时候被想起来 | 只写"检查日志"——Claude 判断不出何时该用 |
allowed-tools |
权限边界 | 给 Bash 却只需要 Grep,扩大了不必要的动作面 |
| 正文 | 读什么 → 判什么 → 报什么 → 修什么 | 只写"检查一下",没有判定标准也没有修复动作 |
description 那一行尤其要注意:它写的是 Use when the diff touches error handling or logging.——给了触发条件,不只是功能描述。Skill 不被触发的头号原因就是描述里只说了"我能干什么",没说"什么时候该找我"。
正文的收尾也值得学:Report each violation with file:line, then fix it。先定位再修复,而且明确了修复动作是"补上缺失的 request ID、把 payload 从日志调用里剥掉"。这就是第二节里说的那个区别——这一句才让它从"检查"变成了"循环"。
allowed-tools 里给的是 [Read, Edit, Grep],没有 Bash。一个只需要读代码和改代码的检查,就不该拿到执行命令的能力。这是顺手做对的安全默认。
六、核心:同一个检查,放在哪里跑,决定它有没有用
这是全文最有价值的一节。检查内容写好之后,真正的设计决策是它怎么被触发——原文给了四挡:独立调用、内嵌、链式、PR 门禁。
四挡不是"越往右越好",每一挡都有它该待的位置,也都有明确的退出信号。
6.1 独立调用(Standalone):你自己想起来调
产物已经存在之后,你有意识地调它一次。
适合什么:跨流程、但不是每次都要跑的检查。原文举的三个例子界定得很准——提交前的安全扫描、发 PR 前的无障碍审计、全仓库的许可证头核对。共同点是:你希望在很多工作流里都能用到它,但不希望它在每次代码改动时都触发。
代价:每次调用都是一个你必须记得去用的轮次。
换挡信号:你已经在每次改动后都跑它了。这时这套流程就赢得了一个永久的位置——内嵌它,或者串进链条里。
6.2 内嵌(Embedded):生产它的 Skill 顺手就查了
检查归属于一个特定工作流,那就让这个工作流自己跑完它。
最简单的做法是在生产 Skill 的正文末尾追加一行。原文的例子是一个 React 组件脚手架 Skill:
# .claude/skills/scaffold-component/SKILL.md
---
name: scaffold-component
description: Scaffold a new React component under src/components/, including the component file, its co-located test, and an index export. Use when the user asks to create a new component.
allowed-tools: [Read, Write, Edit, Bash, Glob]
---
# Scaffold a new React component
Given a component name (PascalCase), create the following under `src/components/<Name>/`:
1. `<Name>.tsx`: function component with a typed props interface and a default export.
2. `<Name>.test.tsx`: React Testing Library test that renders the component and asserts it mounts without throwing.
3. `index.ts`: re-export the default and any named exports.
Follow the patterns in `src/components/Button/` as the reference. Match the import alias style (`@/components/...`) used throughout the codebase.
# code continues...
After creating the component file, run eslint on it and
address any errors before reporting completion.
真正的改动只有最后两行。address any errors before reporting completion——在回报完成之前把错误处理掉,一句话就把"生成"和"验收"绑在了一起。
怎么确认内嵌生效:在一个全新任务上调用这个 Skill,看新增的那一步有没有出现在输出里。如果没有,说明 Skill 的 description 或前面的指令没能把追加的检查带进来——问题在上下文,不在你追加的那行文字。这个排查方向很实用,因为直觉上人会去改追加的那句话,而实际上该改的往往是描述。
硬性限制:内嵌只对你能编辑的 Skill 有效——你自己写的,或者装在项目级、SKILL.md 由你掌控的。内置 Skill 和插件托管的 Skill(升级时会被覆盖那种)不适用这个模式,对它们要改用链式。
什么时候别用:检查横跨多个工作流的时候。那种检查要做成独立调用,才能从任意上下文里叫它。
6.3 链式(Chained):把习惯变成契约
一个 Skill 在自己结束时调用下一个,多道验证过的交接就串成了端到端。
原文透露了 Anthropic 的 Claude Code 团队自己的日常链条,可读性很高:
/code-review找bug →/simplify清理 diff → 一个/verifySkill 确认端到端行为 → 如果改动碰了 UI,再由自定义的/designSkill 对着DESIGN.md里的规范检查一遍。
四道关卡的分工很清楚:查正确性、查冗余、查行为、查规范。最后一道还带条件触发(if the change touched UI)——不是每一环都无条件跑,这个细节让链条的成本可控。
链式还有一个特殊用途:给你改不了的 Skill 加验证。做法是套一层自己的包装 Skill:
# .claude/skills/safe-refactor/SKILL.md
Run /simplify on the current diff first.
When /simplify finishes, invoke /verify-no-public-api-changes.
原文对这个模式的总结是一句很漂亮的话:
原本是一个习惯(“我总是在
/simplify之后跑/verify"),现在变成了一份契约("/simplify结束时一定会跑/verify")。
习惯和契约的差别,就是"靠你记得"和"不靠你记得"的差别。
什么时候别用:当这几步足够独立、你有时候只想跑其中一个的时候。链式是拿灵活性换自动化,这是一笔明确的交易,不是免费升级。另外原文专门提醒:链式验证会推高 token 开销,大规模铺开之前先小范围试。
6.4 PR 门禁:验证从个人设施变成团队设施
链条在你自己的改动上跑稳之后,同一套流程可以搬到每个 PR 上。
这一挡带来的变化不是技术性的:同事的改动会过和你一样的关卡,不管他有没有想起来调这条链。原文的措辞是——验证到这一步就不再是个人基础设施,而成了团队基础设施:
你为了省自己每周两分钟而写下的那个检查,现在每个人每次改动都省两分钟。
什么时候别上:链条还在频繁调整的时候先按住。因为一旦挂到 PR 上,你每次调整都会变成一个团队可见的事件——今天规则松一点、明天严一点,成本是所有人的注意力。
这四挡放在一起看,有一条清晰的主线:从左到右,你交出去的东西在变。独立调用你交出的是检查内容,内嵌交出的是调用时机,链式交出的是整段流程编排,PR 门禁交出的是团队的执行标准。
七、六步落地路径
原文最后收敛成一套流程,并强调"不管你自动化什么、在什么环境里,这个流程都是同一套”:
- 挑出这周你做得最多的那个手动收尾动作;
- 先试内置的
/verify,看它是否已经能解决; - 用大白话把流程写下来,按交接给第一天入职的同事那样写;
- 交给
skill-creator,或者自己往.claude/skills/丢一个 Markdown 文件; - 在一个新任务上调用它,确认检查真的出现在输出里,不对就迭代;
- 试着串链,拼出端到端的验证流。
这六步里最容易被跳过的是第 5 步。写完 Skill 就当它生效了,是很常见的错误——Skill 是否被触发,取决于 description 有没有写清触发条件,而这件事只能靠在真实任务上验一次来确认。
八、三点补充判断
原文写得克制,有几处可以往前推一步。
第一,验证条件要尽量可判定。 “看起来正常"不能作为通过标准,因为代理无法据此判断要不要再来一轮。verify-log-hygiene 那个例子之所以立得住,是因为它的通过标准是二值的:日志调用里有没有 request ID、有没有 payload。设计检查时可以先问一句:Claude 靠什么信号判断这一轮该停还是该重来? 答不上来,这个检查就还没写完。
第二,description 是路由,不是文档。 从"内嵌不生效要去查描述"这个排查建议就能看出来,Claude Code 里Skill 的调度高度依赖描述字段。写描述时正确的心态不是"介绍这个 Skill 是什么”,而是"在什么条件下应该想起我"——Use when the diff touches error handling or logging 这种句式,比任何功能描述都有效。
第三,成本要一起算。 链式验证会让每次改动跑更多轮次,token 开销是实打实的。合理的姿势是:高频、廉价、确定性的检查(linter、类型检查)尽量内嵌;昂贵的端到端验证放在链条尾部或 PR 门禁上,并配合条件触发(像官方那条链里的"碰了 UI 才查设计规范")。验证覆盖率和运行成本之间没有免费午餐,只有分层。
九、结语
这篇文章的落点其实不在自动化本身,而在注意力的去处。原文最后一句说得很清楚:
你能替Claude 写下来的东西越多,它的回应第一次就落在你想要的位置上的概率就越高。那些你不再需要反复摆弄的修正,把你的注意力释放给那些没有任何 Skill 能替你写下来的、只属于你的工作。
值得区分的是两种"节省":一种是让 Claude 干得更快,另一种是让你不必再检查它干得对不对。前者省的是机器时间,后者省的是你的注意力——而注意力才是稀缺的那一样。
所以真正该问的问题不是"我能让 Claude 自动化多少",而是——这周我亲手复查了什么,其中哪一条今天就能写下来?
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付