Cron 的 nextRunAtMs 会骗人:调度系统的静默失败比崩溃更危险
OpenClaw 的 GitHub 上报了一个 bug(#12313):Cron 任务按时推进了下次执行时间,但什么都没干。
任务"看起来跑了"——nextRunAtMs 更新到了下一天——但没有产生任何输出,cron action=runs 的历史记录里也空空如也。你手动用 cron action=run 触发,返回 "ran": false, "reason": "not-due",因为时间戳已经跳到下个周期了。
换句话说:系统告诉你一切正常,但任务根本没跑。
静默失败的损害
这种 bug 比崩溃更麻烦。
崩溃是显式的。进程挂了,systemd 重启了,日志里有堆栈。你知道出了问题,能查、能修。
静默失败是隐式的。任务的元数据在更新,调度器在正常运转,Gateway 的日志没有异常。你以为每天早上 8 点的邮件摘要在按时发送,直到三天后才发现收件箱里什么都没有。
对于 Agent 系统来说,Cron 任务通常承担着"后台自动化"的角色:定时检查邮件、定时备份笔记、定时同步日历、定时跑安全扫描。这些任务的核心价值就是"你不用操心,它自己跑"。静默失败直接摧毁了这个前提。
为什么会静默失败
从 #12313 的描述和 OpenClaw 最近几个版本的 CHANGELOG 来推断,问题可能出在"调度-执行"的时序上。
正常的 Cron 执行流程应该是:
到达预定时间 → 执行 payload → 记录 run history → 更新 nextRunAtMs
但实际行为是:
到达预定时间 → 更新 nextRunAtMs → (payload 没执行) → (run history 没记录)
时间戳先更新了,payload 后执行(或没执行)。如果 payload 执行失败了,时间戳已经跳过去了,这个周期的执行就丢了。
2026.2.6 的 CHANGELOG 里有一条修复:"Cron: scheduler reliability (timer drift, restart catch-up, lock contention, stale running markers)"。这说明调度器在多个维度上存在可靠性问题:计时器漂移、重启后的补偿执行、锁竞争、过期的运行标记。
#12313 的报告者用的是 2026.2.3-1,可能还没拿到 2026.2.6 的修复。但从 2026.2.6 的修复清单来看,这是一个系统性问题,不是单个 bug。
Cron 在 Agent 系统里更脆弱
传统的 cron 任务是"执行一条命令"。失败模式很简单:要么执行了,要么没执行。exit code 非零就是失败了。
Agent 系统里的 Cron 任务是"让 Agent 执行一个目标"。失败模式复杂得多:
- Agent 启动了但上下文溢出,任务中途放弃
- Agent 启动了但模型返回错误,fallback 链走到底也没成功
- Agent 启动了但工具调用超时,系统等不下去了
- Agent 启动了,执行了一些操作,但最终结果没有送达目标频道
每种情况都可能表现为"Cron 调度器认为任务跑过了,但实际没完成"。
怎么检测和缓解静默失败
几个实用的做法。
给每个 Cron 任务加"心跳确认"。任务执行完后,往一个外部端点发一个 GET 请求(类似 healthcheck ping)。如果外部端点超过预期时间没收到 ping,发告警。类似 Healthchecks.io 的逻辑。
{
"cron": {
"schedule": "0 8 * * *",
"payload": "完成邮件摘要后,访问 https://hc-ping.com/your-uuid"
}
}
这个方法要求 Agent 在任务末尾主动"签到",但 Agent 不一定会照做——它可能在完成核心任务后就停了,忘记发 ping。所以不能百分百依赖。
检查 run history 而不是 nextRunAtMs。不要用"下次执行时间是否更新"来判断任务是否跑过。去看 cron action=runs 的历史记录。有 run 记录,才算真的跑了。
设定"最大静默时间"告警。如果一个每小时执行的任务,连续 3 个小时没有产出(没有消息发送、没有文件修改、没有 API 调用),触发告警。这需要把 Cron 的执行与可观测数据关联起来。
用 cron action=run --force 手动补偿。2026.2.6 的 CHANGELOG 提到 cron run 现在默认强制执行,用 --due 参数才限制为仅执行到期任务。如果你发现某个任务漏跑了,可以手动触发补偿执行。但这依赖你先发现了漏跑。
调度系统的设计教训
#12313 暴露的根本问题是:先更新状态再执行操作的顺序是错的。
正确的顺序是:先执行操作,成功后再更新状态。或者至少用一个"执行中"的中间状态标记任务,防止"已完成但实际没跑"的假象。
这在分布式系统里是老生常谈:写操作和状态更新不在同一个事务里,就会出现不一致。但在 Agent 系统里,这个问题被放大了——因为"执行操作"本身就不是确定性的。Agent 可能执行一半就停了,可能执行了但结果不符合预期,可能执行成功但送达失败。
一个更健壮的调度流程:
到达预定时间 → 标记状态为 "executing"
→ 执行 payload → 记录 run history(含成功/失败状态)
→ 只有在执行完成后才更新 nextRunAtMs
→ 如果执行超时,标记为 "timed_out",不更新 nextRunAtMs
2026.2.6 的修复里提到了 "per-job execution timeout" 和 "preserve future nextRunAtMs on restart",方向是对的。但从 #12313 的报告来看,问题还没完全解决。
临时排查步骤
如果你怀疑自己的 OpenClaw Cron 任务在静默失败:
- 升级到 2026.2.6 或更新版本
- 运行
openclaw cron action=list,看看nextRunAtMs是否在往前推进 - 运行
openclaw cron action=runs,看看有没有对应的执行记录 - 如果 nextRunAtMs 在推进但 runs 里没记录,说明你踩到了这个 bug
- 用
openclaw cron action=run --force手动补跑漏掉的任务 - 在 Gateway 日志里搜索
cron相关的 error/warn 级别日志,看有没有执行失败的线索
参考链接
- OpenClaw Issue #12313(Cron 静默失败):
https://github.com/openclaw/openclaw/issues/12313 - OpenClaw v2026.2.6 Cron 修复:
https://github.com/openclaw/openclaw/releases/tag/v2026.2.6 - OpenClaw Cron 文档:
https://docs.openclaw.ai/automation/cron-jobs