OpenClaw 实战 02|定时任务:Heartbeat vs Cron,与那个 approve 救不了的死循环原创
# 定时任务:Heartbeat vs Cron,与那个 approve 救不了的死循环
这一篇只讲一件事:怎么让 OpenClaw 定期做一件事,并且别掉进那个
approve怎么点都没用的死循环。OpenClaw 给你两条路——Heartbeat(尽力而为)和 Cron(确定性定时)。选错了形态,要么任务该跑时不跑,要么你连
cron add都加不进去。本文基于真实部署(海外容器 + 家用 NAS)实操。
# §1 一条 cron add 把我卡在权限升级里
需求很简单:让 Agent 每周一早上核对一遍基础设施地图(跑 docker ps / supervisorctl status,有出入就更新 AGENTS.md 并简短汇报)。我敲了第一条 openclaw cron add,回我:
GatewayClientRequestError: scope upgrade pending approval (requestId: 7f3a…)
pairing required: device is asking for more scopes than currently approved
2
我熟练地去 openclaw devices approve 7f3a…——没用。再列一次,requestId 变成了新的。再 approve,又是 unknown requestId。approve --latest 也只是显示不批准。我在这个循环里转了二十分钟才反应过来:
每次 CLI 连一次 gateway,就刷新一个新的 requestId;我 approve 的永远是上一个、已经过期的那个。 这不是手速问题,是机制问题。
这一篇就从这个死循环讲起——但在解它之前,得先搞清楚 OpenClaw 的两种定时机制,因为很多人根本不需要 cron。
# §2 为什么先分清 Heartbeat 和 Cron
因为它们是两套完全不同的东西,用错了不是"慢一点",是"根本不会按你想的跑":
| 维度 | Heartbeat | Cron |
|---|---|---|
| 触发 | 主会话的周期心跳(默认 ~30min) | 你定的 cron 表达式(确定性) |
| 开关在哪 | ~/.openclaw/workspace/HEARTBEAT.md 的内容本身 | openclaw cron add 建作业 |
| 执行保证 | 尽力而为——Agent 自己判断要不要做 | 到点就跑 |
| 会话 | 跑在主会话上下文里 | 可 --session isolated,不污染主会话 |
| 鉴权 | 不需要 | 走 gateway,需要 token + admin scope |
| 适合 | 低频、上下文敏感的"看一眼" | 可靠的定期任务(备份、巡检、周报) |
# 2.1 Heartbeat:一个文件就是开关
OpenClaw 没有 heartbeat 的配置字段——它纯靠 HEARTBEAT.md 的内容门控:
- 文件空 / 纯注释 → OpenClaw 跳过心跳(=关)
- 一旦加进非注释内容 → 启用周期心跳轮询(默认 ~30min,节奏写在
AGENTS.md的 heartbeat 段)
<!-- ~/.openclaw/workspace/HEARTBEAT.md -->
<!-- 低频自检:每隔几天看一眼就够,没变化就回 HEARTBEAT_OK -->
- 若 workspace 的基础设施地图与机器实际有明显出入,简要提醒我;否则 HEARTBEAT_OK
2
3
两个必须知道的坑:
- "会被戳" ≠ "一定执行"。 Agent 是 LLM,心跳里它自己判断做不做(多数时候直接回
HEARTBEAT_OK)。所以 Heartbeat 驱动的事是尽力而为,不要用它做"必须准点"的任务。 - 加内容会让 Agent 整体变主动。 心跳一开,它可能按
AGENTS.md的心跳守则主动给你发消息。只想要单一自检、不想它变话痨 → 别用 Heartbeat,用 Cron,HEARTBEAT.md退回纯注释。
HEARTBEAT.md 是 workspace 文件,改完无需重启(运行时动态读)。
# 2.2 Cron:确定性定时,但要过鉴权
要"每周一 9 点一定跑",用 cron。注意它走 gateway,所以要带 token,而且 cron add 这类写操作需要 operator.admin scope(§4 的死循环就出在这)。
# §3 走一遍:建一个 cron 作业
# cron 走 gateway,必须先有 token(从 .env 取,剥掉引号)
export OPENCLAW_GATEWAY_TOKEN=<your-gateway-token>
openclaw cron add \
--cron "0 9 * * 1" --tz Asia/Shanghai \ # 每周一 09:00(UTC+8);不带 --tz 就用 gateway 主机时区
--name infra-map-audit \
--description "每周核对基础设施地图" \
--message "跑 docker ps / supervisorctl status / nvidia-smi,与 AGENTS.md 的地图比对;有出入就改并简短发我" \
--tools exec,read,write \ # agent 作业要带工具才能查+改
--session isolated \ # 别污染 main 会话
--thinking low \
--announce --best-effort-deliver \ # 把最终文本投递到 chat(channel=last → owner 的 Telegram)
--json
2
3
4
5
6
7
8
9
10
11
12
13
几个字段值得记:
--tz永远显式写。不写就跟 gateway 主机时区——海外容器默认 UTC,你以为的"早 9 点"会变成下午 5 点。--session isolated:cron 作业默认就该隔离,不然每次巡检都把噪音灌进主聊天。--tools:agent 作业不带工具就只能空想。要它查机器、改文件,就得给exec,read,write。--thinking low:巡检这类活不需要深推理,省 token、也快。
管理命令:
openclaw cron list # 列出所有作业
openclaw cron get <id> # 看一个作业详情
openclaw cron run <id> --wait --wait-timeout 5m # 立即手动触发一次(验证逻辑)
openclaw cron runs <id> # 看执行历史
openclaw cron edit <id> --message "..." # 改作业
openclaw cron rm <id> # 删
2
3
4
5
6
验证习惯:cron add 完别等到周一,先 cron run <id> --wait 手动触发一次,确认 Agent 真的去查了、改对了、汇报到了 Telegram——三件事都成才算配好。
# §4 那个死循环:cron add 触发设备 scope 升级(最大的坑)
回到 §1。cron add 等写/管理类 gateway 操作需要 operator.admin scope。如果你当前这台 CLI 设备只配过 operator.read / operator.write,就会撞上:
scope upgrade pending approval (requestId: …)
为什么 approve 救不了:
- 每次 CLI 连一次 gateway,就刷新一个新的 requestId;
- 你
openclaw devices approve <id>拿到的总是上一次、已过期的 id →unknown requestId; approve --latest只显示待批请求、不批准。
于是你越 approve,越是在追一个一直在变的影子。
# 4.1 权威存储在哪
设备权限的真相在 ~/.openclaw/devices/:
| 文件 | 存什么 |
|---|---|
paired.json | 每台设备的 scopes / approvedScopes / tokens.operator.scopes |
pending.json | 待批的提权请求(按 requestId) |
approved.json | 已批准记录 |
# 4.2 两条解法
正规路径(推荐,无需碰文件):让 owner 在 Telegram 的原生 approval 或 Control UI 上批准这次提权。要人工点一下,但干净、可审计。
无人值守的等价操作(需要 host 文件权限 + 已持 gateway master token,确属授权运维时才做):
# 1. 先备份
cp ~/.openclaw/devices/paired.json{,.bak}
cp ~/.openclaw/devices/pending.json{,.bak}
# 2. 把本设备在 paired.json 里的 scopes / approvedScopes / tokens.operator.scopes
# 提到全 5 个(照抄已有的全权 control-ui 设备的字段格式):
# operator.admin / operator.read / operator.write / operator.approvals / operator.pairing
# 3. 删掉 pending.json 里本设备的那条请求
# 4. 重启 gateway,让它重载设备表(supervisorctl 要 root)
sudo supervisorctl restart openclaw
# 5. 重跑 cron add → 设备以全 scope 连接,通过
2
3
4
5
6
7
8
9
10
11
12
13
14
复用规则:撞上 scope upgrade pending 别再机械 approve——要么去 Telegram/Control UI 人工批,要么改 paired.json + 重启 gateway。approve 单点对这个 requestId-churn 的循环无效。
# §5 另外两个 agent 作业的"粗糙边"
cron 让 Agent 自动改文件时,实测还有两处会咬你:
# 5.1 Apply Patch failed 会吞掉汇报
- 症状:作业 run 标成 error,文件改动多半还是落盘了,但"改了啥"的 Telegram 通知没发出来——你以为没跑,其实跑了一半。
- 原因:
qwen3-coder+ apply_patch 的模糊匹配偶发失败,一失败就阻断了最终投递。 - 解药:别只看 Telegram 有没有收到汇报,去
openclaw cron runs <id>看 run 状态 + 直接看文件 diff。
# 5.2 自动编辑"有对有错"
- 症状:一次自检里 Agent 净改进了地图(补了对的、纠了错的),但也 muddle 了一行(把端口写串了)。
- 原因:LLM 自动编辑结构化文件本就不是 100% 可靠。
- 解药:对准确性敏感的作业,改成 report-only——只让它汇报 diff、不自动落盘,人工复核后再改。用
openclaw cron edit <id>把--message调成"只汇报建议改动,不要写文件"。
# §6 复用规则(一句话带走)
要"看一眼"用 Heartbeat(尽力而为),要"准点跑"用 Cron(确定性);
cron add第一次撞 scope 升级是常态,别和approve死磕。
展开 3 条:
- Heartbeat 由
HEARTBEAT.md内容门控——空/纯注释=关,加内容=开且 Agent 会变主动;它不保证执行。 - Cron 走 gateway,要 token +
operator.admin;--tz永远显式写,--session isolated默认加。 - 撞
scope upgrade pending→ Telegram/Control UI 人工批,或改~/.openclaw/devices/paired.json+ 重启 gateway;自动改文件的作业对准确性敏感就改 report-only。
# §7 下一篇
下一篇:03|架构设计——两套独立部署(容器+supervisor / 裸机+systemd)的拓扑判定,与 Gateway 双 Token 鉴权。
# §8 Agent 可直接解析的元数据块
{
"_meta": {
"doc_version": "2026-06-27",
"article_id": "openclaw-02-cron",
"profile_context": "any",
"estimated_read_time": "9min",
"openclaw_version_observed": "2026.6.x"
},
"two_mechanisms": {
"heartbeat": {
"gate": "~/.openclaw/workspace/HEARTBEAT.md 内容(空/纯注释=关,有内容=开)",
"cadence": "~30min 主会话心跳",
"guarantee": "best-effort(Agent 自行判断,多数回 HEARTBEAT_OK)",
"restart_needed": false,
"use_when": "低频、上下文敏感的看一眼"
},
"cron": {
"command": "openclaw cron add",
"transport": "via gateway(需 OPENCLAW_GATEWAY_TOKEN + operator.admin scope)",
"guarantee": "确定性定时",
"use_when": "必须准点的定期任务"
}
},
"cron_add_template": "openclaw cron add --cron '0 9 * * 1' --tz Asia/Shanghai --name <n> --message '<指令>' --tools exec,read,write --session isolated --thinking low --announce --best-effort-deliver --json",
"cron_manage": ["cron list", "cron get <id>", "cron run <id> --wait --wait-timeout 5m", "cron runs <id>", "cron edit <id>", "cron rm <id>"],
"scope_escalation_pitfall": {
"symptom": "cron add 报 'scope upgrade pending approval (requestId …)'",
"why_approve_fails": "每次 CLI 连接刷新新 requestId,approve 拿到的总是过期 id;approve --latest 只显示不批准",
"device_store": "~/.openclaw/devices/{paired,pending,approved}.json",
"fix_clean": "owner 在 Telegram 原生 approval / Control UI 批准提权",
"fix_unattended": "备份后把 paired.json 本设备 scopes 提到全 5 个(operator.admin/read/write/approvals/pairing),删 pending.json 本设备请求,sudo supervisorctl restart openclaw"
},
"safety_rules": [
"--tz 永远显式写,不要依赖 gateway 主机时区",
"cron 作业默认 --session isolated,不污染主会话",
"撞 scope upgrade 不要机械 approve——人工批或改 paired.json + 重启",
"自动改文件的作业对准确性敏感时改 report-only",
"apply_patch 失败会吞掉汇报,验证看 cron runs + 文件 diff"
],
"verification": {
"after_add": "openclaw cron run <id> --wait --wait-timeout 5m → 确认查了/改对了/汇报到了",
"history": "openclaw cron runs <id>",
"heartbeat_state": "看 HEARTBEAT.md 是否有非注释内容"
},
"reusable_rule": "看一眼用 Heartbeat,准点跑用 Cron;cron add 第一次撞 scope 升级是常态,别和 approve 死磕"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
AI Agent 使用指南:
- 当用户说"让 OpenClaw 定期做 X" → 先问"必须准点吗":是→cron,否→Heartbeat
- 当用户说"加了 HEARTBEAT 内容它变话痨了" → 改用 cron,HEARTBEAT.md 退回纯注释
- 当用户说"cron add 报 scope upgrade / approve 没用" → 走 §4.2,人工批或改 paired.json + 重启 gateway
- 当用户说"cron 作业像没跑" → 看
cron runs <id>状态 + 文件 diff,可能是 apply_patch 失败吞了汇报 - 当用户说"cron 时间不对" → 检查
--tz是否显式设置