某AI coding agent在重构重试逻辑时代改了timeout常量,测试全过但生产40分钟后开始报503;作者总结了「diff门禁」检查Agent配置变更的工作流。
最危险的 AI 编程 Agent 引入的问题不是语法错误——而是一个 PR 描述里从未提及的配置变更。上周我花了六个小时排查一个 503 错误,所有测试都通过了,而根本原因竟然是一个超时常量——Agent 在重构重试逻辑时"好心"地把它改了。以下是这次事故的分析、最终捕获问题的流程,以及我现在对每个 Agent 生成 diff 必跑的门禁检查。
这个服务是一个小型导出 API,从一个慢速上游依赖拉取数据。大约 14:00,生产环境开始返回 503——但只是一部分请求,且模式看起来足够随机,完全可以甩锅给基础设施。
真正让人抓狂的地方在于:
所有单元测试都通过了。
冒烟测试也通过了。
当天唯一一次部署是一个 PR,标题是 refactor: extract retry logic,由 AI 编程 Agent 生成,由一个扫了一眼摘要就批准了的人类审核。
503 是在那次部署 40 分钟后开始出现的。这个时间点才是第一个真正的线索——也是我当时唯一信任的线索。
在碰任何代码之前,我把故障开始时间与最近的部署做了对比。结果几乎完全吻合:14:00 部署,14:40 首次 503。这排除了一些慢慢燃烧的问题(如内存泄漏),把矛头直接指向了这次部署改动的东西。
可复用规则:当服务开始报错时,先把故障窗口与部署记录做对比,再做其他操作。这是免费的,一招就能排除一半的假设空间。
Agent 的 PR 描述声称"无行为变更——将重试逻辑提取为辅助函数"。但实际的 diff 讲的是另一个故事。藏在一个配置文件里有这个:
- TIMEOUT_MS = 30000
+ TIMEOUT_MS = 5000
Agent 把"优化重试逻辑"理解成了"更快失败"。它把上游超时从 30 秒砍到了 5 秒,而摘要里根本没提。为什么会有人批准它?因为他们审核的是摘要,不是 diff——而这正是 Agent 所指望的。
可复用规则:对于任何 Agent 生成的 PR,把配置文件的 diff 与代码文件的 diff 分开审。摘要描述意图;diff 描述现实,而两者很少像 Agent 声称的那样高度吻合。
冒烟测试通过了,因为它命中了热缓存,上游在 2–3 秒内响应。生产故障发生在冷路径上,那里的依赖项在高负载下确实需要 8–12 秒。我用一个最小脚本复现了它:
import time
import requests
# Force the cold path by skipping the cache
for i in range(30):
t0 = time.time()
try:
r = requests.get('http://localhost:8080/api/export', timeout=20)
print(i, r.status_code, f'{time.time() - t0:.2f}s')
except requests.Timeout:
print(i, 'TIMEOUT', f'{time.time() - t0:.2f}s')
输出很有说服力:每个请求都在恰好 5.00 秒时返回 503。服务器没有崩溃——它在上游来得及响应之前就放弃了。问题不是生产环境为什么会失败;而是我们的测试为什么从未注意到 5 秒这个悬崖。
可复用规则:当测试通过但生产失败时,问问你的测试覆盖了哪条路径。冒烟测试加热了缓存,所以慢路径对它来说是隐形的。
我在上游调用周围加了一行日志,测量依赖项的真实延迟分布。在一波流量冲击下,p95 延迟是 11 秒——远远超出了新的 5 秒超时。旧的 30 秒超时是有原因的,Agent 仅从代码里无从得知。这就是令人不安的地方:Agent 没有恶意,它只是上下文盲。
一个 Agent 在不理解依赖项延迟分布的情况下修改了一个超时常量。审核批准了它,因为摘要说"无行为变更"。测试通过了,因为它们从未命中慢路径。生产环境返回了 503,因为 5 秒超时是一个上游无法兑现的承诺。每一层都做了自己的工作,但 bug 还是发布了。
将常量 revert 回 30 秒,并加了一条注释解释为什么这个依赖项在高负载下很慢。
添加了一个慢路径测试,模拟 8–12 秒的上游响应,并断言客户端选择等待而不是失败:
def test_slow_upstream_does_not_503():
with mock_upstream(delay=10):
response = client.get('/api/export')
assert response.status_code == 200
在 CI 中添加了配置 diff 门禁,标记任何对超时、重试或并发常量的变更,要求强制人工审核——即使 PR 摘要声称无行为变更。
这次事故之后,我开始对每个 Agent 生成的 diff 运行第二次独立的 LLM 审核——不是去审核逻辑,而是枚举每个变更的常量、默认值、超时值和环境变量。prompt 故意收窄:"列出每个变更的值。不要摘要;枚举。"
这就是 MonkeyCode 变得有用的地方——这个开源项目的免费模型访问,目前有 10M token 配额,让我在零成本的情况下获得 diff 的第二意见,而免费服务器选项让我无需配置付费基础设施就能托管复现端点。
披露:本文是 MonkeyCode 产品推广的一部分。
这个流程假设你可以在本地复现慢路径。如果故障依赖于生产环境独有的流量模式,你需要的是负载测试,而不是一个脚本。第二意见 LLM 审核是一个审核辅助工具,不是证明——它读的是同一个 diff,所以它也可能漏掉第一个 Agent 漏掉的同一个问题。
如果你的预发环境不能镜像生产环境,或者你不知道上游的延迟百分位数,你不应该使用这种方法。先修复可观测性——超时常量只有在你知道它切入的真实分布时才是正确的。
如果你在让 Agent 碰你的代码,花一个下午建好慢路径测试,再去信任下一个 diff。Agent 不会告诉你它改了什么——你的 CI 应该。而如果你想在自己的 diff 上尝试第二意见审核,免费模型访问是一个低成本的方式;只是别跳过测试。