一个 Git diff 细节改动导致 AI review bot 上下文暴增,向所有 PR 倾泻数十条重复评论,两天耗尽千万 token 预算。
随着越来越多团队将 AI 评审机器人接入 CI,失败的焦点从模型质量转移到了流程管道本身。一个小型团队的评审机器人运行在 MonkeyCode 免费模型访问和一台免费服务器上,开始对每个 Pull Request 重复轰炸大量评论,并在两天内耗尽了 1000 万 token 的额度。模型是无辜的;故障出在一个 Git 命令上——它悄然改变了机器人能看到什么。本次复盘从症状到根因再到修复方案,并附上一份防止同类故障的守卫脚本。
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
流水线平稳运行了数周。一次常规的依赖升级之后,一夜之间发生了三件事:
每个打开的 PR 都收到了四十条甚至更多评论,其中许多完全相同("use optional chaining here" 出现在了不相关的行上)。
原本预计能撑一个月的免费 token 额度,在两天后几乎见底。
CI 评审时间增加了两倍,因为模型收到的上下文远超任何人类评审员所能容忍的范围。
团队的第一反应是怪模型。这个直觉是错的,而证明它错了花不到一个小时。
团队用同一个模型、手动跑同样的评审 prompt,但输入是一份干净的、手写的 diff。输出合理、简洁、有针对性。模型没有退化;是输入变了。
这是复盘的核心教训:当 AI 流水线出现问题时,先隔离变量。模型是最后才应该怀疑的组件,而不是第一个,因为 prompt 和上下文是最容易悄然出问题的部分。
团队通过在流水线中加入日志步骤,抓取了机器人实际发送的准确输入。日志立即揭示了问题:那份 diff 文件在只改了 120 行的 PR 上达到了 18000 行。
# capture what the bot actually sent
git log --oneline -5
git diff origin/main HEAD > /tmp/review.diff
wc -l /tmp/review.diff
# output: 18432 /tmp/review.diff
diff 包含了整个上游历史的变更,而不仅仅是分支自身的提交。机器人在评审作者从未触碰过的代码,并对所有内容发表评论。
根因:两点式与三点式
CI checkout 使用了深度为 1 的浅克隆以节省时间和磁盘空间。评审脚本原本使用三点式写法 git diff origin/main...HEAD,它与 merge-base 对比,只显示分支自身的变更。这种写法需要本地存在 merge-base 提交,而浅克隆里没有。
有人通过改成两点式写法 "修复" 了由此产生的错误 git diff origin/main HEAD,它直接比较两个分支尖端。随着 main 分支不断前进,这个命令会把分支分叉以来 main 添加的每一个提交都包含进来。依赖升级和模型没有任何关系;它只是触发了一次新的克隆,暴露了浅克隆的限制。
# three-dot diff: needs the merge-base commit locally
git diff origin/main...HEAD # empty or fatal: bad object in a shallow clone
# two-dot diff: compares tips directly, includes unrelated upstream changes
git diff origin/main HEAD # thousands of lines the author never wrote
修复分为两部分:显式计算 merge-base,以及在调用模型前守卫上下文大小。
# fetch the base commit explicitly, then diff against it
git fetch origin main --depth 1
BASE=$(git merge-base origin/main HEAD || git rev-parse origin/main)
git diff "$BASE" HEAD > /tmp/review.diff
# guard against context explosion
LINES=$(wc -l < /tmp/review.diff)
if [ "$LINES" -gt 2000 ]; then
echo "diff too large ($LINES lines); skipping review" >&2
exit 0
fi
# rough token estimate before sending anything
BYTES=$(wc -c < /tmp/review.diff)
EST_TOKENS=$((BYTES / 4))
echo "estimated tokens: $EST_TOKENS"
守卫比具体命令本身更重要。一条悄无声息把 18000 行发给模型的流水线,终将烧穿任何 token 额度,无论免费还是付费。团队还添加了一行日志,每次运行都记录 diff 大小和 base commit SHA,这样下次回归会直接体现在 CI 输出里,而不是体现在 token 余额上。
一份可复用的调试清单
同样的调查模式适用于任何 AI 辅助流水线的故障:
在流水线外复现。用干净的输入对同一个模型跑同样的 prompt,以隔离变量。
捕获准确输入。在模型调用前,记录 prompt、diff 和元数据。
在模型之前检查数据。核对 diff 大小、base commit、文件数量和 token 估算值。
一次只改一个变量。依赖升级是红鲱鱼;真正的变化是克隆深度。
为上下文大小添加守卫。对 diff 行数的硬限制,把悄无声息的额度消耗变成响亮的早期失败。
此方法假定故障出在流水线而非模型上。没有日志的团队应该在调试其他任何东西之前先添加捕获步骤。免费层适合中小型仓库,但拥有巨大 diff 的大型仓库会触发守卫并按设计跳过评审。浅克隆行为在不同的 Git 版本和 CI 提供商之间存在差异,因此 merge-base 回退机制应在实际使用的 CI 提供商上测试。最后,自动化评审即使输入了正确的 diff,也不能替代安全关键路径上的人类评审。
模型没有变差;是 diff 变了。一个两点式 Git 命令把一个专注的评审机器人变成了嘈杂的评论员,而 token 额度为这个错误买了单。想要复现此故障模式的团队可以用自己的仓库跑一下守卫脚本,而免费层让实验成本可控。下次评审机器人行为异常时,先检查输入再怪模型。
For further actions, you may consider blocking this person and/or reporting abuse