打开网易新闻 查看精彩图片

你的后台监控每天凌晨3点准时报警,WhatsApp机器人像上了闹钟一样重启,从每天1次变成4次,再到7次。你加了健康检查、修了重连逻辑、甚至怀疑过网络层——但根因藏在一个从未被清空的变量里。

这不是网络故障,不是内存泄漏,是一个lastMessageAt时间戳在 reconnect 时被反复"继承"了旧值。开发者花了4天定位,最后发现修复只需要加一行代码。

从"能跑就行"到"每天7次重启"

从"能跑就行"到"每天7次重启"

故事开始于一个低流量的WhatsApp AI代理账号。消息不多,但稳定性要求极高——每次重启意味着潜在的消息丢失和用户投诉。

最初的异常很隐蔽:机器人偶尔重启,日志显示"499 reconnect"。开发者以为是网络波动,加了健康监控(health-monitor)做兜底。第4天,监控开始提前拦截僵死的 socket,499 循环消失了。

但问题只是被掩盖,没被解决。

真正的诡异之处在于重启间隔的"坍缩":第一天4小时,第二天2小时,第三天1.5小时。像某种倒计时在加速。

 watchdog 的误判逻辑

watchdog 的误判逻辑

WhatsApp 库内置了一个看门狗机制:如果N分钟内没有收到消息,就触发重连。默认的N是30分钟,藏在源码里的tuning.messageTimeoutMs配置项——官方文档根本没提。

打开网易新闻 查看精彩图片

看门狗触发时会做两件事:清空status.lastInboundAt,然后启动重连。但它漏了一件关键的事:没清空status.lastMessageAt

重连初始化时,代码会拿status.lastMessageAt来重新填充active.lastInboundAt。如果这个时间戳没被清空,新连接一上来就背着"几分钟前收到过消息"的旧账。

看门狗立即评估:"上次消息在[旧时间戳],距今已超过30分钟。"触发。重启。循环。

每次重启都从同一个 stale timestamp 重新播种,所以循环永远不会自己打破——直到人工重启网关。

为什么间隔越来越短

为什么间隔越来越短

这个机制完美解释了观察到的"坍缩"现象。

第一次重启是合法的:socket 真的静默了30分钟。但重启后,lastMessageAt保留了重启前最后一条消息的时间戳——可能是几小时前的。

随着当天循环重复,这个时间戳越来越旧。每次重启后的"可用窗口"越来越短,直到趋近于零。开发者形容这就像"信用卡最低还款,越还欠得越快"。

健康监控在第4天介入后,提前掐掉了僵死 socket,打破了循环链条。但这只是止血,没动病灶。

打开网易新闻 查看精彩图片

两行代码的修复方案

两行代码的修复方案

根因修复很简单。看门狗处理函数里加一行:

当前行为:
status.lastInboundAt = null
triggerReconnect()

修复后:
status.lastInboundAt = null
status.lastMessageAt = null ← 缺失的一行
triggerReconnect()

或者在重连初始化时做防御性处理,不依赖可能被污染的状态字段。

作为临时缓解,开发者把messageTimeoutMs从30分钟调到90分钟——对低流量账号来说,30分钟的空闲阈值确实过于激进。这不是根治,但减少了看门狗误触的频率,且不依赖健康监控的兜底。

一个未被文档化的配置项

一个未被文档化的配置项

整个排查过程中最耗时的不是定位逻辑,而是确认tuning.messageTimeoutMs这个配置项真的存在。它不在 OpenClaw 的配置参考里,开发者是在 channel runtime 源码里翻到的。

默认30分钟的设定,显然是为高流量场景设计的。AI代理、客服机器人这类"说话少但得一直在线"的用例,成了边缘情况。

这也解释了为什么这个问题没有被大规模报告——大多数WhatsApp商业账号的消息密度足够高,30分钟内总有消息进来,stale timestamp 没机会累积到触发阈值。

你的监控里有没有类似的"时间戳继承"隐患?那些"能跑就行"的兜底逻辑,是在解决问题,还是在推迟问题?