在大多数人的 OpenClaw 部署里,定时任务(Cron)就像是机器的内脏运动。前一天谈话记录总结在凌晨 1 点醒来,个人知识库维护在周日触发。你听得见显卡风扇在 3 点钟突然加速的轰鸣,但当你问OpenClaw要一份系统定时任务清单,或者问它最近一次系统任务执行情况时,它可能会象个手忙脚乱的实习生一样,紧张兮兮地回复你:“没有找到相关命令”,或者“没有权限执行”。
其实任务肯定执行过了,问题在于谁能看到它在跑什么。

如果你不能随时回答“哪些任务在排队”、“任务定义是什么”、“上次留下了什么证据”,那自动化就不是你的工具,而是一个你无法审计的投机系统。
我们需要把这种“观察监督权”从黑箱里拽出来。
第一步:确认控制面的真实入口
人们对于自己不了解的事物通常有两种反应:夸大其辞,或者哂然一笑。互联网上对待OpenClaw的态度与此严丝合扣:等待看笑话的人,和把权限全部甩给OpeClaw的人。这两种态度都有问题。“未历其境,难知其味”。不是躬身亲尝,不可能了解AI Agent的发展。而全权托付AI Agent的人,出了事又难免觉得它盛名之下,其实难负。
举个例子:OpenClaw只是个AI Agent的框架,具体操纵它的是大模型。训练大模型时的语料里可没有OpenClaw这个产品。所以它对于系统命令的习惯都是基于Linux的记录,比如,要求列出系统定时任务,它很自然就会输入指令:crontab -l。但实际上,在OpenClaw内部,这个需求对应的指令是:node /app/openclaw.mjs cron list 。OpenClaw找不到crontab -l这个指令,就会报告“没有找到相关命令”,任务失败。不了解这个系统的人可能因此沮丧,觉得这个AI Agent什么也做不了。其实只是你没找到做对事情的路径。想象一下人类世界,如果让一个没有经过严格训练的普通人坐到F-15飞机的座舱,面对满屏花花绿绿的指示灯和琳琅满目的按钮,他又能做什么呢?
所以,不是AI Agent不好用,是你需要先了解它能做什么,怎么做。而不是期待它能解决所有事情,或者认为它什么也做不好。
转回定时任务这个话题,它是我们用好OpenClaw的核心任务。所以三个核心指令是必须十分清晰的
- 定时器里有什么任务?列一个清单出来
- 定时器里某一个具体的任务的详细情况什么?
- 最近一次触发定时器的任务完成情况如何?
确认程序位置
docker exec -it openclaw sh -lc 'ls -la /app/openclaw.mjs'
确认数据路径
docker exec -it openclaw sh -lc 'ls -la /home/node/.openclaw/cron'
路径映射如果错位,就会制造出“文件不存在”的幻觉。
第二步:构建三条稳定的“治理接口”
我们不指望 Agent 能学会复杂的 Linux 命令,我们要给它三条确定的物理出口。这些脚本要放在 workspace/skills/bin 目录下。
首先要创建目录:在宿主机执行(会写入持久化目录):
mkdir -p /opt/openclaw/data/workspace/skills/bin
1. cron_list —— 确认系统的节律
这不只是一个定时器任务清单,它是系统还在呼吸的证明。通过拉出这个清单,你会知道系统每天会定时做些什么任务。
cat > /opt/openclaw/data/workspace/skills/bin/cron_list.sh <<'SH'
#!/bin/sh
set -eu
node /app/openclaw.mjs cron list
SH
chmod +x /opt/openclaw/data/workspace/skills/bin/cron_list.sh
2. cron_show —— 任务的骨骼解剖
如果你想知道某个任务到底是干什么的,你需要查阅 jobs.json。我们用一段 Python 代码来实现它,因为 Python 在处理 JSON 时比 Shell 更像一把精准的手术刀。
cat > /opt/openclaw/data/workspace/skills/bin/cron_show.sh <<'SH'
#!/bin/sh
set -eu
JOB_ID="${1:-}"
python3 - <<'PY' "$JOB_ID"
import json, pathlib, sys, re
job_id = sys.argv[1].strip()
if not job_id:
raise SystemExit("usage: cron_show.sh <jobId>")
# 允许 uuid 或你这种 jobId 字符串(你现在是 uuid)
if not re.fullmatch(r"[0-9a-fA-F-]{8,64}", job_id):
raise SystemExit("invalid jobId format")
p = pathlib.Path("/home/node/.openclaw/cron/jobs.json")
data = json.loads(p.read_text(encoding="utf-8"))
jobs = data.get("jobs", data)
cand = None
if isinstance(jobs, dict):
cand = jobs.get(job_id)
if cand is None:
for v in jobs.values():
if (v.get("id") or v.get("jobId") or v.get("name")) == job_id:
cand = v; break
elif isinstance(jobs, list):
for v in jobs:
if (v.get("id") or v.get("jobId") or v.get("name")) == job_id:
cand = v; break
print(json.dumps(cand, ensure_ascii=False, indent=2))
PY
SH
chmod +x /opt/openclaw/data/workspace/skills/bin/cron_show.sh
3. cron_last_run —— 寻找机器留下的证据
执行本身是短暂的,但痕迹是永恒的。这个脚本会翻遍所有的运行记录,告诉你最近一次发生了什么。
cat > /opt/openclaw/data/workspace/skills/bin/cron_last_run.sh <<'SH'
#!/bin/sh
set -eu
JOB_ID="${1:-}"
python3 - <<'PY' "$JOB_ID"
import json, pathlib, sys, re
job_id = sys.argv[1].strip()
if not job_id:
raise SystemExit("usage: cron_last_run.sh <jobId>")
if not re.fullmatch(r"[0-9a-fA-F-]{8,64}", job_id):
raise SystemExit("invalid jobId format")
runs_dir = pathlib.Path("/home/node/.openclaw/cron/runs")
if not runs_dir.exists():
raise SystemExit("runs dir not found: " + str(runs_dir))
latest = None
latest_ts = -1
def try_update(obj):
global latest, latest_ts
if not isinstance(obj, dict):
return
if obj.get("jobId") != job_id:
return
ts = obj.get("ts") or obj.get("runAtMs") or 0
try:
ts = int(ts)
except Exception:
ts = 0
if ts > latest_ts:
latest_ts = ts
latest = obj
# 扫描所有文件,不限制扩展名;支持:
# - 单个 JSON 文件
# - JSON Lines(每行一个 JSON 对象)
for p in runs_dir.rglob("*"):
if not p.is_file():
continue
try:
text = p.read_text(encoding="utf-8", errors="ignore")
except Exception:
continue
# 快速过滤:文件里不含 jobId 直接跳过
if job_id not in text:
continue
# 先尝试整个文件当 JSON
try:
obj = json.loads(text)
try_update(obj)
continue
except Exception:
pass
# 再按 jsonl 逐行解析
for line in text.splitlines():
if job_id not in line:
continue
line = line.strip()
if not line or line[0] not in "{[":
continue
try:
obj = json.loads(line)
except Exception:
continue
try_update(obj)
if latest is None:
print("no run found for jobId:", job_id)
else:
out = {
"ts": latest.get("ts"),
"jobId": latest.get("jobId"),
"status": latest.get("status"),
"summary": latest.get("summary"),
"sessionId": latest.get("sessionId"),
"sessionKey": latest.get("sessionKey"),
"durationMs": latest.get("durationMs"),
"nextRunAtMs": latest.get("nextRunAtMs"),
"delivered": latest.get("delivered"),
"deliveryStatus": latest.get("deliveryStatus"),
}
print(json.dumps(out, ensure_ascii=False, indent=2))
PY
SH
chmod +x /opt/openclaw/data/workspace/skills/bin/cron_last_run.sh
第三步:权限边界的再分配
现在,你需要在 /opt/openclaw/data/exec-approvals.jso 中完成最后一步:治理。
不要给 Agent 所有的权限。你应该通过白名单(allowlist)明确告诉它,只能动这三个脚本。
JSON
"allowlist": [
{ "pattern": "/home/node/.openclaw/workspace/skills/bin/cron_list.sh" },
{ "pattern": "/home/node/.openclaw/workspace/skills/bin/cron_show.sh" },
{ "pattern": "/home/node/.openclaw/workspace/skills/bin/cron_last_run.sh" }
]
这不是在限制它的能力,而是在定义它的边界。通过这种方式,你并没有扩大系统的攻击面,你只是把“观察权”固化在了这三个路径上。
第四步:更新AGENTS.md
你需要让OpenClaw更新它的执行规则,也就是记录到AGENTS.md。在与它的对话里输入以下内容。然后尝试这几个命令,检验你的成果。
将这三条指令加入你的系统规则里
当需要查看定时任务:只运行
/home/node/.openclaw/workspace/skills/bin/cron_list.sh
当需要查看某个 job 详情:只运行
/home/node/.openclaw/workspace/skills/bin/cron_show.sh <jobId>
找该 job 最近一次 run 的 summary:只运行
/home/node/.openclaw/workspace/skills/bin/cron_last_run.sh <jobId>
结论:从执行者到规则制定者
这三段脚本不是简单的工具,它们是你的审计接口。
当自动化到点触发,OpenClaw 会读取 jobs.json,创建一个隔离的会话,生成运行记录,并尝试交付结果。任务结束后,自动化似乎消失了。OpenClaw事后是无法访问一个删除的session,了解这些定时任务信息的。但因为有了这三个脚本,它变成了一种可审计的存在形式。
你不再是那个盯着终端屏幕发呆、祈祷任务正常跑通的“工具搬运工”。你现在是规则的定义者。风扇仍然在转,但现在,你能听懂它的声音了。