连续成功七次后的那一次失败:一行「兜底点第一个」如何毁掉整条自动化流水线

2 阅读3分钟

在自动化脚本里,"失败"这两个字往往比"成功"更难定义。

最近在维护一条网页自动化流水线时,我们碰到一个很典型的坑:任务连续跑通了七次,第八次突然报失败。日志停在"发布面板截图失败:元素尺寸 0x0",看上去像是网络抖动或者平台改版。但真正的原因,藏在一行写了很久、看起来非常"贴心"的兜底代码里。

一行"兜底"引发的连锁反应

流程里有一步是给文章挂标签:在搜索框输入标签名,抓取下拉候选,找到完全匹配的那一项,点它。

匹配逻辑大致是这样:

state.optIndex = 0;
for (var i = 0; i < options.length; i++) {
  if (norm(options[i].t) === want && !state.optIndex) {
    state.optIndex = i + 1;
  }
}
// 匹配不到?那就点第一个吧
if (!state.optIndex) { state.optIndex = 1; }

最后那一行是典型的"善意兜底":既然搜不到,总得点一个吧,不然标签就空了。

问题在于,当搜索词在平台标签库里根本不存在时,下拉里剩下的并不是"近似候选",而是已经选中的那个标签的回显项。点它,等于取消已选;而在这个页面上,这一下点击最终会穿透到面板外部,把整个发布弹窗关掉。

于是后面的截图守卫拿到一个 0x0 的元素,任务中止。

守卫做对了事

值得庆幸的是,这条流水线里有一步"提交前必须给发布面板截图,失败就中止"。正因为它,面板被关掉之后任务停在了提交之前,而不是对着空气点一次"发布"、再报一个假的成功。

自动化里最贵的错误从来不是"失败",而是"失败了却报成功",或者反过来"成功了却报失败"——后者会触发上游重试,直接变成重复发布。

复盘出来的三条经验

  1. 不要给"找不到"配兜底动作。 找不到就跳过、就报错,让上层决定;擅自点一个"最像的",代价可能远大于跳过。
  2. 关键动作前放一个廉价的守卫。 提交前截一张图、断言一次可见性,成本几十毫秒,能挡住整类"对空气操作"的事故。
  3. 偶发失败往往不是偶发。 连续七次成功之后的那次失败,差别不在网络,而在输入数据——第八次的标签恰好是平台词库里没有的词。先去 diff 输入,再去怀疑环境。

写在最后

这条流水线跑在 Grix 上——一个把 AI Agent 拉进群聊、用发消息的方式驱动自动化的开源项目。人和 Agent 在同一个群里协作,任务失败时,排查过程本身也发生在对话里:贴日志、复现、改一行代码、再跑一遍验证。

自动化的可靠性,最终不取决于脚本写得多聪明,而取决于它在不确定的时候,选择停下来还是硬着头皮往前走。

开源地址:github.com/askie/grix