Cron 的 nextRunAtMs 会骗人:调度系统的静默失败比崩溃更危险

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 任务在静默失败:

  1. 升级到 2026.2.6 或更新版本
  2. 运行 openclaw cron action=list,看看 nextRunAtMs 是否在往前推进
  3. 运行 openclaw cron action=runs,看看有没有对应的执行记录
  4. 如果 nextRunAtMs 在推进但 runs 里没记录,说明你踩到了这个 bug
  5. openclaw cron action=run --force 手动补跑漏掉的任务
  6. 在 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
← 返回博客列表