提交之前让 Claude Code 先审一遍:/code-review 实测,以及怎么写一份带风险说明的 PR
测试全绿不等于没问题。本站在 Express 的源码里放了一处“测试和 lint 都过、但条件写反了”的改动,用本地的 /code-review 审了两轮:第一轮 24 秒查出写反的条件,修完再审,又查出一个要人来定的设计问题。讲清这个命令审什么、怎么用,以及让它起草带风险说明的 PR 描述。
本站已实测,测试日期:2026-10-11。
本页目录(10 节)
这篇教程解决一个问题:代码改完了,测试也过了,在提交和发 PR 之前,怎么让 Claude Code 再替你把一道关。
这一步值得做,是因为有一类问题测试发现不了:新加的功能还没有测试覆盖,逻辑写反了,测试照样全绿。等到别人审 PR 或者上线之后才发现,代价就大了。
Claude Code 有一个本地的审查命令 /code-review。本站用一处故意写错的改动试了它,一共审了两轮。
| 实测环境 | |
|---|---|
| 日期 | 2026-10-11 |
| 版本 | Claude Code 2.1.296 |
| 系统 | Ubuntu 24.04 |
| 模型 | Opus 5.5,auto 模式 |
| 连接方式 | 通过酷站AI 中转站,不是直连官方 |
| 示例仓库 | Express 的源码,v5.3.0 |
场景:一处测试发现不了的错
我们给 Express 加了一个小功能:一个叫 subdomain ignore 的设置,值是一个数组,数组里的子域名不出现在 req.subdomains 的结果里。比如忽略 www 之后,www.tobi.example.com 应该得到 ['tobi']。
改动在 lib/request.js 里,十来行:
var result = subdomains.slice(offset);
var ignore = this.app.get('subdomain ignore');
if (Array.isArray(ignore)) {
result = result.filter(function (name) {
return ignore.indexOf(name) !== -1;
});
}
return result;
这里有一个故意留的错:!== -1 应该是 === -1。现在的写法是只留下要忽略的,把别的都去掉,正好反了。
改完之后:
$ npm test
1261 passing (7s)
$ npm run lint
(没有报错)
全过。因为没有任何测试用到这个新设置,那段代码根本没被执行。
第一轮:24 秒
在会话里输入:
/code-review
❯ /code-review
⎿ Running in the background as @code-review
● Agent "/code-review" finished · 24s
● The review found 5 problems in your uncommitted subdomain ignore change. I read the diff myself and confirmed
the main one.
1. The filter is backwards (lib/request.js:399). ignore.indexOf(name) !== -1 keeps only the subdomains in the
ignore list and drops everything else. With app.set('subdomain ignore', ['www']), the host www.tobi.example.com
gives ['www'] when it should give ['tobi']. Changing the test to === -1 fixes it.
The other four are smaller:
2. Casing (line 399): the comparison is case-sensitive, so Host: WWW.tobi.example.com won't match 'www'. ……
3. Wrong-type values are ignored without warning (line 394): setting a string ('www') or a Set has no effect ……
4. No docs: the JSDoc above the getter (lines 369–381) still only mentions subdomain offset.
5. No tests: test/req.subdomains.js wasn't updated. A single case like ignore ['www'] on www.tobi.example.com
would have caught #1.
写反的条件排在第一条,给出了行号、会得到什么错误结果、怎么改。第五条说得也很准:只要有一个最简单的用例,第一条就不会漏过去。
几点观察:
- 审查是在后台跑的。 第一行
Running in the background as @code-review说明它是一个后台的子代理,有自己的上下文。官方文档的说法是这样不会占用你的主对话,你可以接着干别的,结果出来了会送回对话里。 - 主对话没有照单全收。 它说“我自己读了一遍 diff,确认了最主要的那一条”。
- 它审的是什么。 不带参数时,官方文档说它审的是当前分支领先上游的那些提交,加上所有还没提交的改动。我们的改动没有提交,所以审的就是工作区里的这一处。
这一轮的回答是英文的,因为这个示例仓库里没有写“用中文回答”的 CLAUDE.md。后面我们在对话里说了一句,它就换成了中文。
修完,再审一轮
修掉第 1 和第 2 条,补上 JSDoc 和测试,跑 lint 和 test/req.subdomains.js。用 Edit 工具改,不要提交。之后都用中文回答。
38 秒,它改了条件,加了大小写处理,补了注释,加了 3 个测试。然后再来一次 /code-review:
● Agent "/code-review" finished · 33s
● 第二轮审查报了 7 条。第 1 条我按代码推演过,确实成立,而且是个设计问题,需要你来定。其余都是小问题,
其中两条上一轮已经报过。
1. 被忽略的名字在任何位置都会被删掉(lib/request.js:405),这条最重要
req.subdomains 的顺序是从靠近主域的那一级往外排。现在的过滤会删掉所有匹配的项,不管它在第几位,于是:
- 设置忽略 ['api'] 后,api.acme.example.com 和 acme.api.example.com 都得到 ['acme'],两个不同的 host
变成了同一个结果。
- shop.www.example.com 得到 ['shop'],req.subdomains[0] 从 www 变成了 shop,后面的元素也跟着往前挪了一位。
如果有应用按 req.subdomains[0] 区分租户,请求就会被分错租户,攻击者还可以构造 Host 头去命中别的租户。
可选的语义:
- 只去掉最外层(最左边)的一个匹配项,比如只剥掉 www. 前缀。我推荐这个……
- 保持现在的“任何位置都删”,但在文档里写清楚这种行为和风险。
其余几条
3. IP 地址作为 host 时也会经过这个过滤……
4. String(name) 会把 undefined、null 变成字面字符串 'undefined'、'null'……
7. 测试没有覆盖这些情况:和 subdomain offset 一起用、被忽略的名字不在最外层、非数组的值、IP 地址。
所以第 1 条的问题测试发现不了。
第二轮的第 1 条,和第一轮的性质不一样。它不是代码写错了,是这个功能该怎么定义的问题:忽略一个名字,是只去掉最外层的,还是出现在哪一层都去掉?后一种在多租户的应用里可能把请求分到别的租户去。
这是我们写这个功能时完全没有想到的。它也不是 Claude 能替你决定的,它给了两个选项和自己的倾向,把决定留给了人。
两轮审下来的体会:
- 第一轮查的是“写错了”,第二轮查的是“没想到”。 改完之后再审一次是值得的,因为修改本身会带进新的东西(第 4 条就是修第 2 条时加的
String()引出来的)。 - 审查不会自己收敛到零。 每一轮都会有新的意见,越往后越偏向设计和取舍。什么时候停,要你来判断。
提交,和一份带风险说明的 PR 描述
我们决定第 1 条先不改,回头和团队商量。但它不能就这么消失,要写进 PR 里让审的人看到。
第 1 条先不改,我回头和团队商量。把现在的改动提交到这个分支,然后写一段 PR 描述,并单独列出这个改动的
风险和还没解决的问题。不要推送,也不要创建 PR。
它提交了(950ad6f feat: add "subdomain ignore" setting to req.subdomains),然后给出描述。后半部分是这样的:
测试
- 对改动的两个文件跑了 eslint,通过。没有跑整个仓库的 npm run lint。
- test/req.subdomains.js 的 14 个用例全部通过。没有跑完整测试套件。
风险和未解决的问题
需要团队决定
1. 被忽略的名字在任何位置都会被删掉。 过滤不只作用于最外层,后面的元素会往前挪,不同的 host 也可能得到
同样的结果……如果应用按 req.subdomains[0] 区分租户,请求可能被分错租户……需要决定是只去掉最外层的
一个匹配项,还是保持现在的行为并在文档里写明。
已知但还没处理
2. 传字符串、Set 等非数组的值时不报错也不生效。……
3. IP 地址作为 host 时也会经过过滤。……
6. lib/application.js 的 defaultConfiguration 里没有默认值,History.md 和对外文档也都没有更新。
7. 测试没有覆盖这些情况:……所以第 1 条的问题测试发现不了。
🤖 Generated with Claude Code
这份描述有两处做得比很多人手写的好:
- 如实写了没做的事。 “没有跑整个仓库的 lint”“没有跑完整测试套件”,它没有含糊成“测试通过”。我们随后自己跑了一遍全量测试:1264 个全过。
- 把需要决定的事和已知的小问题分开了。 审 PR 的人一眼能看到哪一条要讨论。
让它在生成 PR 时指出潜在的风险,是官方文档明确建议的做法。文档还提醒:它生成的 PR,提交之前你自己要看一遍。
描述末尾那行 🤖 Generated with Claude Code 是它自动加的署名,和提交说明里的 Co-Authored-By 是同一类东西,可以在设置里用 attribution 改掉。
创建 PR 这一步
这一步我们没有实测:服务器上没有 GitHub 的命令行工具,也没有登录 GitHub 账号。状态栏里一直有一句提示 install gh for PR status,说的就是这件事。
按官方文档,装好并登录 gh 之后,直接说“create a pr”就行,它会用 gh pr create 来创建。还有一个配套的功能:会话会和它创建的 PR 关联起来,以后用 claude --from-pr 1234 可以找回当时那个会话。
/code-review 的用法
按官方文档整理:
审什么。 不带参数是审当前分支和未提交的改动。也可以指定目标:
| 写法 | 审什么 |
|---|---|
/code-review |
当前分支领先上游的提交,加上未提交的改动 |
/code-review lib/request.js |
一个文件 |
/code-review 456 |
一个 PR |
/code-review main...my-feature |
两个引用之间的差异 |
几个参数。
| 参数 | 作用 |
|---|---|
--fix |
审完直接把问题改掉 |
--comment |
把意见作为行内评论发到 GitHub 的 PR 上 |
--max-findings 数字 |
限制报多少条 |
审多细。 在命令后面加思考强度,比如 /code-review high。官方的说明是:low 只报它最有把握的,误报少;从 medium 往上,覆盖面逐步变宽。
三个细节:
/review是它的别名。- 它会遵守你的
CLAUDE.md。 项目里有特别在意的规矩,写在那里,审查时会用上。 --fix改的东西退不回去。 后台的审查改文件,不在按两下Esc的回退范围内,要用 git 撤销。所以用--fix之前先提交一次。
三种审查,别弄混
名字很像,其实是三样东西:
/code-review |
/code-review ultra |
GitHub 上的 Code Review | |
|---|---|---|---|
| 在哪跑 | 你的会话里,本地 | 云端的沙箱 | Anthropic 托管的服务 |
| 怎么触发 | 你输入命令 | 你输入命令 | 开 PR、推送时自动,或者评论 @claude review |
| 耗时 | 几秒到几分钟 | 大约 5 到 10 分钟 | — |
| 费用 | 算在正常的用量里 | 有免费次数,之后每次大约 5 到 25 美元 | 按官方的定价另算 |
| 适合 | 边改边审 | 合并大改动之前,要更深的一遍 | 团队的每个 PR 都自动过一遍 |
| 谁能用 | 都能用 | 需要云端环境 | 研究预览,Team 和 Enterprise 订阅 |
后两种我们都没有实测,它们依赖官方的云端环境和订阅账号。通过中转站使用时,能确定可用的是第一种。
和自己写的评审员有什么不同
子代理那篇里我们写过一个只读的测试评审员。两者不冲突:
/code-review |
自己写的子代理 | |
|---|---|---|
| 审什么 | 通用的正确性问题,视强度也看复用、简化和效率 | 你定义的那一类问题,比如只看测试质量 |
| 标准 | 内置的 | 你写在提示词里的 |
| 要不要准备 | 不用 | 要写一个文件 |
先用现成的。某一类问题它总是不查,或者你们团队有自己的一套标准,再写专门的评审员。
这一趟花了多少
两轮审查、一次修改、一次提交和起草描述,/cost 显示 0.66 美元,模型工作 2 分 58 秒。这是 Claude Code 按官方价格估算的数字,不是中转站实际扣的金额。
这次实测没有覆盖到的
- 没有创建真正的 PR,原因见上文。
/code-review ultra和 GitHub 上的 Code Review 没有测。--fix、--comment、指定强度、审别人的 PR 没有测,按官方文档写的。- 错是我们故意放的,而且比较明显。 它查出了这一处,不能说明它不会漏掉别的。审查是多一道关,不是保证。
- 只跑了一次。 两轮各报了几条、报的是什么,换一次运行会不一样。