OpenClaw 定时任务不触发怎么查:从网关进程、时区到被自动禁用的排查路径
定时任务是那种「配好就不再看」的东西,所以出问题的时候特别难受:你不知道它是从哪天开始不跑的,也不知道是没触发、跑了报错,还是跑成功了但结果没送到你眼前。翻日志又是一大片,无从下手。
OpenClaw 把这块叫 automations(自动化),CLI 是 openclaw automations,openclaw cron 保留为同一组命令的别名。有意思的是,官方原本有一个独立的 automation troubleshooting 页面,现在这个页面只剩一句「本页已迁移」,排查内容被并回了 Scheduled Tasks 正文的 Troubleshooting 一节。也就是说,排查线索是和机制说明混在一起的——你得先知道调度器是怎么跑的,才知道该看哪一层。
这篇按「先分层、再逐项核对」的顺序整理一遍。先说结论式的分层:任务不触发和任务无声,是两类完全不同的故障,走的排查路径也不一样。
第一步:先把「没触发」和「触发了但你没看见」分开
两者的界线很清楚——看有没有 run history(运行记录)。文档写明,每次自动化运行都会创建一条 background task 记录,任务定义、运行期状态和运行历史都持久化在 OpenClaw 共享的 SQLite 状态库里,所以重启不会丢调度。
openclaw automations list
openclaw automations runs --id <jobId> --limit 20
runs里没有这段时间的记录 → 属于「没触发」,往下看调度器、网关、时区、表达式。runs里有记录、状态是ok,但你没收到东西 → 属于投递问题,直接跳到本文后面的送达一节。- 有记录但状态是
error或skipped→ 又是另外两条线,skipped尤其容易被误判成「没跑」。
有一个坑要先提:默认的 list 只显示启用中的任务。如果任务已经被自动禁用,你在默认列表里根本看不到它,会误以为任务丢了。要用 openclaw automations list --all 才能看到被禁用的那些。
第二步:走一遍官方给的命令阶梯
文档在 Troubleshooting 一节直接给了一串命令,从外到内一层层缩小范围,照着敲就行:
openclaw status
openclaw gateway status
openclaw automations status
openclaw automations list
openclaw automations runs --id <jobId> --limit 20
openclaw system heartbeat last
openclaw logs --follow
openclaw doctor
顺序是有讲究的:前两条确认进程活着,第三条确认调度器本身在工作,中间两条落到具体任务,最后才是日志和体检。日志放在后面,是因为在没缩小范围之前看日志基本等于大海捞针。关于日志和诊断开关怎么打开,可以另看网关诊断开关与日志怎么开。
三个最常见的「压根没触发」
官方 Troubleshooting 里「Automations not firing」条目一共就列了四点,前三点值得逐条对:
一是调度器被关了。 检查配置里的 cron.enabled,以及环境变量 OPENCLAW_SKIP_CRON。文档明确写着,关闭自动化的方式就是 cron.enabled: false 或 OPENCLAW_SKIP_CRON=1。这个环境变量很容易在某次调试时加上去就忘了摘。顺带一提,自动化关掉之后,heartbeat 的监视任务也不会 tick,且不会有后备的心跳定时器兜底。
二是网关没有持续在跑。 这条是机制层面的硬约束:自动化运行在 Gateway 进程内部,不在模型里。网关不在跑,调度就不会触发。如果你的网关本身起不来,那属于另一个问题,参考网关起不来:锁文件、端口与后台进程。
三是时区。 文档把时区的规则写得很细,我整理成表:
| 场景 | 实际按什么时区解释 |
|---|---|
--at 带时区偏移的时间戳 | 按时间戳自带的时区 |
--at 不带时区偏移 | 按 UTC |
--cron 且没写 --tz | 按网关宿主机时区 |
--cron 且写了 --tz America/New_York | 按该 IANA 时区 |
--every / --on-exit | 不接受 --tz,写了会被拒绝 |
heartbeat 的 activeHours | 按配置的时区解析规则 |
最典型的翻车是:你在本机测通了,部署到服务器上,服务器是 UTC,于是「每天早上 7 点」变成了下午三点。给 cron 表达式显式加 --tz 基本能免掉这一类扯皮。
第四点是 reason: not-due。这个不是故障——它表示你是用 openclaw automations run <jobId> --due 手动触发的,而任务当时还没到点。想强制立刻跑一次,去掉 --due 就行;想等它跑完再返回,加 --wait(默认超时 10m,轮询间隔 2s,状态 ok 退出码为 0,error、skipped 和等待超时都是非 0)。
表达式本身就写错了:日和星期是「或」
这个坑不报错,只是默默跑多,特别隐蔽。OpenClaw 的 cron 表达式由 croner 解析,当「日」和「星期」两个字段都不是通配符时,croner 的匹配规则是任一字段命中即触发,而不是两个都要满足。这是标准 Vixie cron 的行为。
# 你想表达的:每月 15 号,且当天是周一,早上 9 点
# 实际的效果:每月 15 号早上 9 点,加上每个周一早上 9 点
0 9 15 * 1
文档给的数字是:这条表达式一个月大概会触发 5 到 6 次,而不是 0 到 1 次。要真的取交集,用 croner 的 + 前缀写成 0 9 15 * +1;或者只按一个字段调度,另一个条件放到任务的 prompt 或命令里自己判断。
反过来的情况也有——你以为「没准时触发」,其实是整点被主动错峰了。文档写明:分钟字段为 0 且小时字段是通配符的循环任务(也就是「每小时整点」这一类),会被自动错开最多 5 分钟,用来削峰。想要精确时刻用 --exact,想自定义窗口用 --stagger 30s(仅 cron 类型的调度支持)。所以看到 9:03 才跑,不一定是坏了。
任务被自动禁用了,而你在默认列表里看不见
这是「某天开始就彻底没动静」最常见的解释。调度器有一个不需要你开启的安全兜底:
| 触发条件 | 结果 | 记录的原因 |
|---|---|---|
| 时间型循环任务连续 10 次执行失败 | 自动禁用 | consecutive-failures |
| 调度计算反复失败 3 次 | 自动禁用 | schedule-errors |
失败原因记在 state.autoDisabled.reason 里,任务所属的 agent 会收到一条带安全化原因和恢复命令的通知,原始错误仍留在自动化历史里。一次成功运行会重置连续失败计数。修好病根之后,用 openclaw automations enable <jobId> 重新启用,启用动作会一并清掉记录的原因和失败连击数。
在被自动禁用之前,失败是有退避的:一次性任务遇到瞬时错误(限流、过载、网络、超时、服务端错误)走内置重试计划,遇到永久性错误直接禁用任务;循环任务的连续执行错误按 30 秒、60 秒、5 分钟、15 分钟、60 分钟的节奏退避,下一次成功后重置。所以「越跑越稀」也是预期内的表现,不是又一个 bug。
状态是 skipped:本地模型端点预检把它拦下了
skipped 很容易被当成没跑。它有一个专门的来源:隔离任务(isolated)开跑之前,OpenClaw 会对配置里 api: "ollama" 和 api: "openai-completions" 且 baseUrl 属于回环地址、私网地址或 .local 的供应商做可达性预检。预检会沿着这个任务配置的 fallback 链走一遍,只有所有候选都不可达时才把这次运行标为 skipped,并给出明确错误,而不是硬发一次模型调用。如果你用 --fallbacks "" 写成严格模式,那这个检查就只针对主模型。
两个附带的行为值得记住:探测结果按端点缓存 5 分钟(不是按任务、也不是按模型),所以一堆任务共用一台挂掉的本地推理服务时,只会付出一次探测成本,而不是打出一串请求;另外,预检导致的 skipped 不会增加执行错误退避计数。如果你希望重复的 skipped 也发告警,要显式打开 failureAlert.includeSkipped。
条件触发器与 stream 型任务的「安静」
如果任务挂了条件脚本(trigger script),没有运行记录反而是正常的:脚本返回 fire: false 时,调度器会持久化评估状态和计数器,然后重新排期,不创建运行历史。所以看 runs 是空的,不代表调度没在评估。
这类任务有几个硬边界:触发型调度内置 30 秒最小间隔;每次评估有 30 秒墙钟预算和最多 5 次工具调用;返回的 state 上限 16 KB。还有一条容易忽略的:如果被触发的那次运行失败了,脚本返回的新 state 不会被持久化——下一次评估看到的还是旧状态,于是可能再次触发。官方因此建议把脚本写成只读检查,动作全部放进 payload。文档还提醒了监控设计上的陷阱:要围绕「可行动的状态」写 watcher,一个在自身检查失败或超时时就安静下来的 watcher,坏掉时看起来和健康时一模一样。
stream 型任务(--stream-command)另有一条自我保护:连续 5 次运行时长都短于 60 秒,任务会被置为 error 状态并走正常的失败告警路径,需要手动重新启用才能解除这个重启上限。
还有一个前置条件常被忘掉:条件触发脚本和 script 类型的 payload 都需要 cron.triggers.enabled: true,stream 调度同样要求它。这个开关默认关闭,而且不是随手能开的——文档用警告框写明,打开它等于允许这些脚本以所属 agent 的**完整工具策略(包含 exec)**无人值守地执行代码,只有当所有能创建自动化任务的 agent 都可信到这个程度时才该打开。
从 heartbeat tasks 迁移过来的任务
老版本的 heartbeat scratch 支持一个结构化的 tasks: 块。现在运行时不再把 tasks: 文本当调度解析,heartbeat scratch 只剩监视用的散文。如果你的任务是在那个年代写的,升级后要跑 openclaw doctor --fix 把每一条转换成普通的、可编辑的 main-session 自动化任务;doctor 会保留原来的间隔和上次运行时间,先建任务再删块,重复运行也能安全收敛同样的声明键。
迁移过来的任务执行时仍走带守卫的 heartbeat 唤醒:活跃时段、最小间隔、洪水控制、忙碌重试都还在。这里就有一条排查线索——落在 heartbeat 活跃时段之外的那次调度会被跳过,顺延到该任务的下一次触发。看起来像「白天跑、晚上不跑」,其实是设计如此。cron 和 heartbeat 两条路子怎么选,可以对照cron 与 heartbeat 怎么选。
归档会话,会连带禁用绑定在它上面的任务
这条很容易漏。归档一个会话(Control UI,或用 sessions.patch { key, archived: true, expectedSessionId })会禁用所有绑定到该会话的启用中任务——包括它的隔离 cron:<jobId> 会话、session:<key> 目标,以及投递或唤醒用的 sessionKey 通道。
关键在于:恢复会话不会自动恢复这些任务,恢复还要求同一个可观察身份,任务要自己用 openclaw automations enable <jobId> 一个个开回来。Control UI 侧栏里,带启用中绑定任务的会话会显示一个时钟角标,可以拿来反向确认。
跑了,但结果没送到
如果 runs 里状态是 ok,那就是投递侧的问题。文档列的几条对照着看:
| 现象 | 可能的原因 |
|---|---|
| 完全没有兜底发送 | 投递模式是 none,本来就不做 runner 兜底发送(agent 自己在有聊天路由时仍可用 message 工具直接发) |
| 外发被跳过 | channel / to 缺失或无效 |
| Matrix 上发不出去 | 房间 ID 大小写敏感,复制过来或老任务里被转成小写的 delivery.to 会失败,要改成 Matrix 里那个精确的 !room:server 或 room:!room:server |
报 unauthorized / Forbidden | 渠道凭据问题,投递被拦 |
| 什么都没发,也不报错 | 隔离运行只返回了静默令牌(NO_REPLY / no_reply),OpenClaw 会同时抑制直接外发和兜底的排队摘要 |
另外,在配置了多个渠道的宿主机上,用 automations add|create 建的、或用 automations edit 改过的隔离 announce 任务,必须设置 --channel <id>,除非你用带供应商前缀的 --to 或保留的会话路由选中了渠道。走 webhook 投递的话,还要注意所有外发自动化 webhook 都过严格的 SSRF 守卫:回环、私网、链路本地等特殊用途目标默认被拒,要放行得在 cron.webhookSsrfPolicy.allowedHostnames 里写精确的主机名或 IP。webhook 侧的问题另见webhook 收不到消息怎么排查。
这套排查覆盖不到的地方
有几类情况,上面这条路径帮不上忙,得说清楚:
超时的判定。 文档给了几个默认值——设置了 --timeout-seconds 就按它算;否则隔离/分离的 agent-turn 任务先受调度器自己的 60 分钟看门狗约束,再轮到底层的 agent-turn 超时(agents.defaults.timeoutSeconds,默认 48 小时);命令型 payload 默认 10 分钟;脚本型 payload 默认 300 秒、上限 900 秒。启动阶段卡住会走专门的分阶段超时,报出类似 cron: isolated agent setup timed out before runner start 的信息。但具体多久算「太久」,取决于你自己的任务。
模型侧的失败。 运行级的 agent 失败即使没有回复负载也会计为任务错误,从而累加错误计数并触发失败通知,不会被当成成功清掉。可这只告诉你「失败了」,模型为什么失败要回到供应商与故障转移那条线去查。
外部调度器驱动的情况。 如果你不是用 automations,而是从系统 cron 或别的调度器直接调 openclaw agent,那这套排查基本不适用。文档在这里的建议是给它包一层硬杀升级:GNU timeout 用 timeout -k 60 600 openclaw agent ... 而不是裸的 timeout 600 ...,-k 是排空不掉时的最后手段;systemd 单元用 SIGTERM 作停止信号并配 TimeoutStopSec 宽限窗口。另外,原网关运行还活着时复用同一个 --run-id,重复的那次会被报成 in-flight,而不是再起一次运行。
保留期。 cron.sessionRetention 默认 24h(设 false 或 "0h" 关闭)会清理隔离运行的会话条目;运行历史每个任务保留最新 2000 条终态记录。查很久以前的一次故障,可能已经查不到了——所以任务开始出问题的时候,早点把 runs 捞出来存一份,比事后回溯划算。
延伸阅读
- 从头读起:OpenClaw 架构总览:网关、Agent、渠道、节点、插件五层各管什么,出问题该往哪查
- 本专题共 40 篇,完整分组目录见专题页
- OpenClaw 定时任务和 heartbeat 到底选哪个:官方五维对照表逐行拆解
- OpenClaw webhook 收不到消息:先分清三条链路,再按注册、鉴权、载荷逐段定位
本文依据 OpenClaw 官方仓库(github.com/openclaw/openclaw)docs/ 下的官方文档整理,核对日 2026-08-17。
我们没有安装或运行过 OpenClaw,因此不涉及界面外观、操作手感与实测耗时的任何描述;
文中的默认值、命令与配置项均为文档口径,不构成对实际运行结果的保证。
该项目迭代很快,请以仓库最新内容为准。接入即时通讯平台前,请自行确认所在平台的规则与合规要求。