Hermes 定时任务推送 bug 修复记录

Hermes 定时任务推送 bug 修复记录

问题发现

定时任务跑得好好的,结果却投不出去。

具体场景是这样的:用户在飞书里设置了一个 cron job,到了时间应该收到一条任务完成通知。结果消息就是发不出去——没有报错,没有异常,就是静默失败。但只要重启一下 Hermes 服务,马上就好了。

一开始怀疑是配置问题,或者飞书 token 过期。但看了一圈发现都不是:手动触发同样的任务可以正常推送,只有定时触发的会失败。问题很明确——和"时间"有关,和"重启"有关。

分析过程

先看日志。日志里没有任何 error,只有一条 info 级别的 "dispatching message",然后就没有然后了。说明代码走到了发送这一步,但底层没有真正投出去。

看代码。定时任务在 gateway 内部运行时,需要通过 gateway 的 event loop 和 live adapter(这里是飞书)来投递消息。这个路径上,gateway 使用的是这样的异步模式:

``python

loop = asyncio.get_event_loop()

result = await loop.run_in_executor(None, run_sync)

`

这是 Python 3.9 之前的做法,官方早就标记废弃了。问题在于:定时任务在 gateway 进程内运行,本质上需要访问 gateway 的 event loop 来做 async 操作。旧模式在某些时序下会让 async context 无法正确找到 loop,特别是服务跑了一段时间之后。

重启服务能解决,是因为进程刚启动,loop 状态干净,没有历史遗留的 context 问题。

修复方案

换掉废弃的 API,用 asyncio.to_thread()

`python

之前

loop = asyncio.get_event_loop()

result = await loop.run_in_executor(None, run_sync)

之后

result = await asyncio.to_thread(run_sync)

`

asyncio.to_thread() 是 Python 3.9+ 的标准方法,专门用于在线程中运行同步代码,同时正确处理 async context。代码更简洁,行为也更正确。

改动只有 3 行,全在 gateway/run.py 里。不影响 standalone cron daemon 模式,只影响在 gateway 内部运行定时任务的场景。

复盘

这个 bug 属于典型的"老代码还能跑就不动"心理。get_event_loop() 配合 run_in_executor` 的写法在 Python 3.9 之前满地都是,但官方在 Python 3.10 里正式废弃了它。如果你也在用类似的模式,建议早点换掉——它不会每次都出问题,但出一次就很难排查。

---

修 bug 的人最懂这种痛。

Hermes Agent 架构深度解析 2026-04-10

评论区