diff 里明明没有它,审查评论却落在 notify.js 上

阿里把内部用了两年的代码审查工具 open-code-review 开源出来这件事,我犹豫了几天要不要认真看。中文圈这几天的文章基本都在复述 README:混合架构、省 token、F1 超 Claude Code。这些说法对不对先放一边,我真正关心的是另一件事------我现在的日常工作流里,AI 写的代码越来越多,人审的时候最容易漏的从来不是单个文件里的错,而是文件和文件之间的契约。

比如改一个配置字段名。config.js 和 publisher.js 我都记得同步,但第三个文件里那行 config.retryCount,它不在这次 diff 里,我的眼睛也不会往那儿去。这种雷 single-file 审查天然失明,等炸了往往是在三天后的线上。

ocr 官方说法里让我停下滚动的是这个:"Agent 能读完整文件、搜整个代码库、翻看同一次改动里的其他文件来拿上下文,做的是深审而不是扫一眼。"是不是真的,得喂一个真实的雷试试。

雷是我自己埋的,但不是靶场

我没有搞那种埋十个坑再统计检出率的评测------那种实验做出来数据漂亮,但跟我日常遇到的场景差得远。我照着自己上个月真实踩过的坑,写了个 20 来行的定时发布小服务,就 4 个文件:

arduino 复制代码
// config.js(基线版本)
const config = {
  webhook: 'https://hooks.example.com/publish',
  publishHour: 20,        // 每天 20 点发布
  retryCount: 3,          // webhook 失败重试次数
  dryRun: false,
};

publisher.js 读 publishHour 判断发布时间,notify.js 负责 webhook 通知,里面有一行重试上限校验:if (attempt > config.retryCount) { return false; }。基线 commit 之后,我做了每个开发者都做过的事:把 publishHour 改成 publishWindow: { start: 20, end: 22 },把 retryCount 改名成 maxRetries。 改完自己过了一遍 diff:config.js 改了,publisher.js 同步了,名字对得上,逻辑自洽。notify.js 我压根没打开------它不在这次改动里。 这就是要审的 commit。diff 统计两行:2 files changed, 8 insertions(+), 7 deletions(-)。

装配顺利,第一脚踩在协议上

工具本体是个 npm 包,装完就一个 ocr 命令:

perl 复制代码
npm i -g @alibaba-group/open-code-review
ocr version
# open-code-review v1.12.12 (182898cf) linux/amd64
# built at: 2026-10-05T06:44:01Z

1.12.12 是三天前发的版本,更新挺勤。模型我自己有 deepseek-flash 的 key,配置走命令行三件套。这里踩了今天第一个坑:

arduino 复制代码
ocr config set llm.url https://api.deepseek.com
ocr config set llm.auth_token sk-****
ocr config set llm.model deepseek-flash
ocr llm test
# Error: llm request failed: POST "https://api.deepseek.com/v1/messages": 404 Not Found

404 的路径是 /v1/messages------它默认拿 Anthropic 协议在敲 DeepSeek 的门。DeepSeek 的 API 是 OpenAI 兼容的,补一行关掉就好了:

bash 复制代码
ocr config set llm.use_anthropic false
ocr llm test
# ✓ Connection test successful
# ✓ Tool-call round trip verified

第二行 "Tool-call round trip verified" 我盯着看了两秒。审查 agent 的核心动作就是读文件、搜代码这些工具调用,连通性和工具调用链路分开验证,这个设计我认可。

跑审查:前半段全是报错,我以为是白跑了

css 复制代码
ocr review --commit HEAD

输出开头先给了我一个 warning,然后是连串的失败:

vbnet 复制代码
warning: git 2.34.1 is older than the minimum supported version 2.41.0
[ocr] 2 file(s) changed, reviewing 2 in /root/ocr-demo
[ocr] Skipping LLM grouping for 2 file(s), 15 changed line(s) --- reviewing as one group
[ocr] Skipping plan phase for group "config.js,publisher.js" (2 file(s), max 11 lines, total 15 lines)
[ocr]   ▶ code_search search_text=retryCount
[ocr]   ✘ code_search failed: git grep failed: exit status 129: error: unknown option `max-count'
[ocr]   ▶ code_search search_text=publishHour
[ocr]   ✘ code_search failed: git grep failed: exit status 129: error: unknown option `max-count'

我的沙盒是 git 2.34.1,低于它要求的最低版本 2.41.0,git grep 的 --max-count 参数不认,agent 发起的每一次 code_search 全部失败。连续七八个 ✘ 刷下来,我心想这把算是废了,搜索都不好使,你还怎么跨文件找。 然后它干了这么一件事(中间省略了若干重复动作):

ini 复制代码
[ocr]   ▶ file_find query_name=notify
[ocr]   ✔ file_find (2ms)
[ocr]   ▶ file_read file_path=notify.js
[ocr]   ✔ file_read (3ms)
[ocr]   ▶ file_read file_path=main.js
[ocr]   ✔ file_read (3ms)

code_search 挂了,它退而求其次用 file_find 按名字找文件,再一个个 file_read 读内容。notify.js------那个不在 diff 里的文件------被它主动翻开了。还把跟这次改动毫无关系的 main.js 也读了一遍。

先把确定性那部分说完,因为前面两行 Skipping 比看起来有价值:15 行的小改动,它直接跳过了"用 LLM 给文件分组"和"plan 阶段",整包丢给 agent。这两个决策是工程逻辑做的,一个 token 都没花。换成一个 50 文件的大 diff,这里的分组就会启动,按官网的说法每组是隔离子任务并发跑。省 token 的账有一半是在这些"不调模型"的判断上省出来的。

评论落在了它不该看见的文件上

24 秒后,第二轮 agent 报告"没有新发现",提前收敛。最终评论一条,我原样贴过来(它定位在 config.js 第 5 行,也就是 retryCount 被改名的那行):

ruby 复制代码
─── config.js:5-5 ───
[bug · critical] Renaming `retryCount` to `maxRetries` is a breaking config contract
change, but `notify.js` still reads `config.retryCount`:

if (attempt > config.retryCount) { return false; }

After this rename `config.retryCount` is `undefined`, so `attempt > undefined`
is always `false` and the retry limit is silently disabled (infinite retry on
failure). Please either update `notify.js` to use `config.maxRetries`, or keep
a backward-compatible alias.

四个要素全齐:改名的性质(breaking config contract)、没跟进的文件(notify.js,diff 外)、失效机制(attempt > undefined 恒为 false)、修复建议(改名或者留兼容别名)。我拿 node 验证了它说的失效行为:

arduino 复制代码
// 模拟改名后的重试校验
const config = { maxRetries: 3 };
function post(attempt) { if (attempt > config.retryCount) return false; return true; }
console.log(post(99));  // true ------ attempt 传 99 也没被拦住,上限形同虚设

1 > undefined 是 false,这个我背得出,但"上线后 webhook 一挂就无限重试、不炸日志也不报警"这个后果链,是我自查 diff 的时候完全没有推到的。评论里 "silently disabled" 这个定性很准,比"会报错"可怕。

token 账单

css 复制代码
Summary: 2 file(s) reviewed, 1 comment(s), ~67970 token(s) used
         (input: ~63505, output: ~4465), cache(read: ~53888), 24s elapsed

67,970 tokens,其中 63,505 是输入、大头走了缓存(53,888 读缓存)。输出只有 4,465。按 deepseek-flash 的价格这一轮是几分钱。官网 benchmark 里同一模型(Deepseek-V4-Pro)跑 ocr 的均值是 394K tokens、跑 Claude Code 是 5,450K,我的小仓库两个数都小一个量级,没法直接对表,但"agent 的动作里有多少是工程逻辑提前算好的"这一点,从输出日志能直接看到------分组、文件筛选、行号定位这些全是本地做的,模型只管看代码和下判断。

顺便说下这次实验的"意外收获":code_search 在我的环境里从头挂到尾,整个搜索能力等于残废,它靠 file_find 加逐个读文件照样把雷挖了。如果 git 版本达标,搜索命中应该更快、token 应该更省------这个数我没法给出,沙盒升不了 git,只能留个话头。

这钱花得值不值

回到我最开始的问题:AI 写代码的时代,人审不住的到底是哪一类错。这次实验给我的答案挺具体的------不是"AI 写的代码质量差",而是自检的目光只会落在 diff 里面的文件上。diff 越是被 AI 批量生产,"单看都对"的假象就越普遍,因为 AI 自己检查的时候也在看 diff。

ocr 这类工具解决的就是这个缝隙:让模型把没进 diff 的文件也翻一遍。24 秒几分钱审掉一个我会带到线上的雷,这个交换我觉得是划算的。至于 F1 那些数字的真伪,我这一把实验背不了书,只能说在我这台 git 都不达标的破沙盒上,它把我自查时漏掉的雷抓出来了------这一点比我自己的眼睛强,就够了。 对了,git 2.41.0 这个最低版本要求,装之前先查一下自己的,别学我,搜索全程残废还在那儿纳闷它怎么这么慢。

相关推荐
ControlRookie2 小时前
SyncBridge:一个 CODESYS 工程与 AI 之间工程师级的同步工具
git·ai编程·codesys·工业软件·controlrookie
xhy_07074 小时前
AI 在同一步上反复打转怎么办?WES Code 循环检测怎么用
人工智能·大模型·ai编程·wes code
孟健6 小时前
200 美元订阅实测:从 Agent 吞吐与缓存机制,算清 Claude 与 OpenAI 的算力账
人工智能·llm·ai编程
HelloWorld0018 小时前
别让上下文撑爆钱包!生产级 Agent 长上下文治理:动态窗口、注意力衰减与摘要状态机实战
ai编程
LEE9 小时前
前端转型全栈 05:SQL 与迁移,AI 写的 SQL 怎么安全上线
前端·后端·ai编程
用户721746588269 小时前
三层时间结构与静音间隔:把 ASR 词级毫秒时间戳变成 SRT 字幕轴
人工智能·ai编程
用户539418729079 小时前
.cursorrules 写了等于没写?我把“让 Cursor 读懂你项目”的规则和上下文整理成一套模板
ai编程·cursor
xiwc10 小时前
AI Helper 实战:从零搭建开源项目的双语 Wiki
开源·ai编程
过客1234510 小时前
从"发现"到"处置":一个无人值守 AI 闭环的完整拆解(85 天真实数据)
后端·agent·ai编程