我们给自己用 AI 写出来的代码做了一次审计
Hover 的大部分代码,都是有一个编码 agent 全程参与、飞快写出来的。这本来就是这个产品的核心卖点,换别的方式来做反倒奇怪。但「又快又有 agent」和「正确」是两码事,而这中间的差距,恰恰是 agent 最看不出自己产出里问题的地方。
于是我们停下功能开发,花一个会话把整个代码库过了一遍:找出死代码、走不到的逻辑、还没触发的 bug,然后一一修掉。这一遍翻出了大约四十个真问题,我们修了其中所有,只留下一个——那是个眼下没用到的工厂函数,但它记录下的结构还有价值,留着成本又低,就留下了。
值得拿出来说的发现,都不是那些一眼就能看见的。
bug 就藏在负责安全的那段代码里
最扎眼的那个,恰恰就躺在代码库里专门负责安全的那部分。Hover 在把请求写进一份要提交的 spec 之前,会先从录制的请求里剥掉凭据。Cookie、bearer token、JSON body 里的 SSN,这些东西在落盘的路上都会被丢弃或掩码。
这个 sanitizer 维护着两份凭据名列表,一份管 URL 查询参数,一份管 JSON body 字段。两份本该一致,实际却对不上。auth 和 apikey 在 URL 列表里有,body 列表里却漏了,于是查询串 ?auth=... 会被掩码,而一个叫 auth 的 body 字段却径直写进了提交的文件。两份「本该保持同步」的列表,就是一个装了定时器的 bug。
我们的改法是让两个匹配器都从同一个交替式(alternation)派生出来,这样凭据名只要加一次,两遍处理就都覆盖到,再也不会各走各的。顺手还发现 body 匹配器只抓字符串值,一个以 JSON 数字形式发出的 SSN("ssn": 123456789)就这么溜了过去,这个也一并修了。教训不在于把正则写得更好,而在于:最该审的代码,往往正是你最信任的那段——因为再没人会去读它。
一个比 bug 本身还糟的修复
有一个发现,是我们自己交付出去的回归。之前我们让一次运行能在 WebSocket 断开时活下来,这样聊天面板在会话中途重连,也不会把 agent 弄死。这改动是好的。可问题是,引擎的 close() 路径从此不再中止这个变得「命硬」的运行,于是关停引擎时,会留下一个孤儿 agent 进程继续活着。我们把这次运行改得更难杀死,却忘了它本该被杀死的其中一个地方。这次审计抓到了它。而用户会以另一种方式撞见它:关掉标签页之后,一个僵尸 claude 进程还在那儿吃 token。
死代码会骗你
更不起眼的收获,是那些删除。一个没用到的 assert import、一批再没人引用的 seed 定义,还有一处本该是正则的控制字符检查,却在源码里被写成了字面字节,结果什么都匹配不到。死代码不是中性的。那处控制字符检查看着像个守卫,其实不是。读这个文件时,你会以为那段输入被处理过了,其实没有——直到我们把这个检查重写成一个真正的 charCodeAt 循环。
这其实就是产品本身,只是把枪口对准了自己
这次演练的意义,不在于「AI 会写出带 bug 的代码」——你自己也会写出带 bug 的代码。没有验证环节的「快」,不过是一份没人审过的 diff;而你想跳过这一环,唯一的办法就是对自己不诚实。
而这正是 Hover 卖的那种纪律。优化环节会把已保存的 spec 重新读一遍,而不是拿初稿就信。API 测试会把你应用发出的真实调用、改掉其中的 ID 再重放一遍,而不是假定「绿色的顺利路径」就代表代码稳。给我们自己的源码做一次审计,是同一个动作,只不过对准的是我们自己。既然我们不会不经验证就交付 Hover 的产出,那也就不该不经验证,就把 Hover 本身交付出去。
在你自己的应用上试试 Hover。
把 Hover 的 MCP 加到你已经在用的编码 agent。它探索你的应用,结晶出你自己拥有的纯 Playwright spec。
npm i -g @hover-dev/mcp && claude mcp add hover -- hover-mcp阅读快速开始 →