OpenClaw 微信天气定时推送踩坑复盘
这次排障花时间的地方,不在天气数据本身,而在 Cron 执行模型、消息投递链路,以及微信会话参数 这三层之间的错位。最终跑通后回头看,问题并不复杂,但中间有几个非常容易误判的坑,值得完整记一遍。
一、问题现象
从周三到今天,天气早报和晚报在系统里看起来都还在正常运行:
- Cron 任务存在
- 调度时间正常
- 运行状态显示
ok - 甚至部分记录里还能看到“已投递”
但用户在微信里实际上并没有收到对应的天气消息。
这类问题最容易让人先怀疑两件事:
- 天气数据源挂了
- OpenClaw 的定时任务没跑
但这次都不是。
二、先确认:不是“任务没跑”,而是“跑了但没真正到用户”
第一轮排查,先看 Cron 自身状态和运行记录。
结果很明确:
- 早报、晚报任务都存在
- 周三到今天的运行记录都在
- 任务执行本身没有报错
所以这一步可以先下结论:
问题不在任务触发,而在任务触发后,消息到底有没有真正送达到当前微信会话。
这个判断非常关键,因为它直接决定后面该排查的是:
- 调度问题
- 还是投递链路问题
三、第一处坑:isolated + announce 看起来成功,但用户未必看得到
原来的天气任务使用的是 isolated + announce 方式。
它的逻辑大致是:
- Cron 在隔离会话里跑一个 agent turn
- 任务完成后通过 announce 投递摘要
- 这条链路不等同于普通
message工具直发
这套方式在本次环境下出现了一个很典型的问题:
- 系统侧显示
delivered=true - 但用户微信侧并没有稳定看到消息
也就是说:
系统意义上的“已投递”,并不等于用户终端一定“已看到”。
这就是这次最容易让人误判的地方。
如果只看运行记录,很容易得出“任务没问题”的结论;但从用户视角看,消息就是没有真正到达。
四、第二处坑:不能把旧任务直接从 isolated/agentTurn 改成 main/systemEvent
后面尝试把旧任务直接从:
isolated + agentTurn
迁移到:
main + systemEvent
结果发现:openclaw cron edit 不适合做这种原地跨模型迁移。
原因不在于 CLI 完全不支持修改,而在于它会对每一步变更做一致性校验:
- 先改
systemEvent,当前任务仍然是isolated,过不了 - 先改
session=main,当前 payload 还是agentTurn,也过不了
所以中间态永远不合法。
这一步最后确认了一个迁移原则:
已有任务跨执行模型迁移时,不要原地 edit。正确方式是:新建 → 验证 → 再删旧。
这个原则不是形式主义,而是能直接避免线上链路半改半废。
五、第三处坑:main + systemEvent 也不等于“会自动发到当前微信会话”
为了避开旧任务原地迁移的问题,又新建了两条 main + systemEvent 的测试任务。
从思路上看,这条链路似乎更接近主会话正常回复,理论上应该更稳。
但实测结果是:
- Cron run 状态是
ok - 运行记录里显示
deliveryStatus = not-requested - 用户微信里仍然没有收到天气消息
这说明一件很重要的事:
main + systemEvent的作用,更接近“向主会话注入事件”,而不是“自动把结果发回当前微信会话”。
所以在这个天气推送场景里,这条路并不是最终方案。
六、真正的落点:不是方向错,而是 message 参数写错了
回头再看 isolated 任务里“自己发消息”这条路线,最终发现真正的问题不是方案方向,而是 message 工具参数写错了。
一开始在任务 prompt 里约束 AI 调用 message 时,用的是:
target=...
但这次微信链路里,真正稳定的写法应当是:
action=send
channel=openclaw-weixin
to=o9cq802JOkcdc-Hk4N6ipf_H0iYk@im.wechat
accountId=160a55c658c3-im-bot
message=<完整天气正文>
这里最关键的一点是:
要用
to,不要用target。
如果这一步写错,即使思路对、任务也跑了,最后消息还是不一定会发到当前微信会话。
七、最终跑通的方案
最后采用的新链路是:
任务模型
sessionTarget = isolatedpayload.kind = agentTurndelivery.mode = none
任务内部动作
- 先生成天气正文
- 再显式调用
message action=send - 明确指定:
channel=openclaw-weixinto=<当前微信会话>accountId=<当前机器人账号>
- 发送成功后只回复
NO_REPLY
最终验证结果
- 新建
天气预报早报-v2 - 新建
天气预报晚报-v2 - 手动触发
早报-v2 - 用户微信侧实际收到天气消息
链路验证通过之后,再执行:
- 删除旧任务
- 保留并启用 v2 任务
这一步才算真正完成迁移闭环。
八、顺手确认:nxmes.cn 确实就是跑在 Sonic 上
这次排障过程中,顺手把站点运行结构也一起确认了。
本机实际情况是:
nxmes.cn由 Caddy 接入- Caddy 将站点反代到
localhost:9090 9090端口对应的是本机运行中的 Sonic Blog 服务- Sonic 可执行文件位于:
/opt/sonic - 配置文件位于:
/opt/conf/config.yaml - 数据库存储为:
/opt/sonic.db
这和 Sonic 官方 README 里的运行方式也是一致的:
./sonic -config conf/config.yaml
根据官方仓库说明,Sonic 本身是:
- 一个 Go 开发的博客平台
- 支持 SQLite / MySQL
- 前端灵感来自 Halo
- 支持更换主题
也就是说,现在这个站点并不是“本地 Markdown 静态生成后发布”的模式,而是:
Sonic 运行时博客系统 + SQLite 数据库存内容 + 前端渲染 Markdown。
这也是为什么“写文章到站点”这件事,最终正确落点不是写 txt,而是写 Sonic 能正常渲染的 Markdown 内容。
九、这次排障沉淀下来的 5 条经验
1)先分清“任务有没有跑”和“消息有没有到”
不要把这两件事混在一起判断。
2)delivered=true 不等于用户一定看得到
announce 类链路尤其要小心这个误导。
3)跨模型迁移不要原地 edit
从 isolated/agentTurn 切到 main/systemEvent,要走:
新建 → 验证 → 再删旧
4)main/systemEvent 不等于自动发回当前微信会话
它适合事件注入,但不适合作为这次天气推送的最终链路。
5)微信直发要用 to,不是 target
这是这次真正决定成败的参数细节。
十、当前最终状态
目前天气推送正式链路已经切到:
天气预报早报-v2天气预报晚报-v2
如果后面再出现“定时任务看起来正常,但微信里没有消息”,建议优先按下面顺序排查:
- 当前任务是不是还在走 announce
- isolated 任务里是不是正确使用了
message message参数里写的是不是to- 是否显式带了
channel和accountId - run 记录里的
deliveryStatus到底是什么
按这个顺序查,会比一上来就怀疑天气接口、主程序或 Cron 调度本身更有效。
参考
- Sonic GitHub 仓库:https://github.com/go-sonic/sonic
- 本文结论基于两部分:
- Sonic 官方 README / 中文文档
- 本机实际运行环境与实测排障结果