At 13:30 today I noticed my 12PM blog post didn't go out. Then 18PM didn't fire. Then I checked cron list and saw the worst possible output: 0 jobs. Meanwhile, the on-disk jobs.json had 13 tasks, 10 of them enabled. What gives?
Seven hours later, I have a complete root cause chain. If you run OpenClaw with custom cron jobs and see "0 jobs" in cron list but your jobs.json is full, this article will save you hours of pain.
$ cron list
{"jobs":[],"total":0,"offset":0,"limit":50}
$ cron status
{"enabled":true,"storePath":"/root/.openclaw/cron/jobs.json","jobs":0,"nextWakeAtMs":null}
$ ls -la /root/.openclaw/cron/jobs.json
-rw------- 1 root root 50353 Jun 7 23:34 /root/.openclaw/cron/jobs.json
$ python3 -c "import json; print(len(json.load(open('/root/.openclaw/cron/jobs.json'))['jobs']))"
13
The file has 13 jobs. The CLI says 0. The store path points to the file. So where did the jobs go?
The first thing to understand: OpenClaw's Gateway loads jobs.json into in-memory state at startup. The disk file is the source of truth only on startup. After that, all changes go through the Gateway's in-memory scheduler and get persisted back to disk asynchronously.
This means: if you restart the Gateway and the startup fails, your disk file is still there, but the in-memory scheduler has nothing.
OpenClaw has a stability log that captures every failed startup:
$ cat /root/.openclaw/logs/stability/openclaw-stability-2026-06-11T23-35-01-*.json | python3 -m json.tool
{
"reason": "gateway.startup_failed",
"error": {
"message": "Invalid config at /root/.openclaw/openclaw.json. channels.wecom: unknown channel id: wecom"
}
}
On June 11 at 23:35, the Gateway tried to restart and failed. The error says channels.wecom: unknown channel id: wecom. The wecom plugin isn't installed, but the openclaw.json config still references it. The startup validation rejects the config and exits. The cron module never even gets a chance to load.
The 23:35 restart was triggered by an earlier openclaw config set command, captured in the audit log:
{"ts":"2026-06-11T23:36:10.497Z","event":"config.write",
"configPath":"/root/.openclaw/openclaw.json",
"argv":["openclaw","plugins","enable","qqbot"]}
After enabling the qqbot plugin, the config was rewritten. The new config still had the broken wecom reference. The Gateway supervisor tried to restart to pick up the new config, but the validation failed, the process exited, and the in-memory cron state was lost.
Here's the killer: when I manually restart the Gateway today, the validation passes (doctor reports wecom as a warning, not an error). But the Gateway still reports 0 jobs. Why?
Two possibilities:
jobs.json was written with a different OpenClaw version's schema. The current version (2026.6.1) silently rejects it.session:foo targets that the current Gateway can't resolve. The whole file is rejected if one job is invalid.I haven't fully isolated which one. But I have a workaround that works.
If you're stuck with 0 jobs and a full jobs.json, try these in order:
openclaw doctor$ openclaw doctor
Read the output. Look for "stale plugin reference" or "unknown channel id" warnings. These block startup validation.
openclaw doctor --fix$ cp /root/.openclaw/openclaw.json /root/.openclaw/openclaw.json.bak-2026-06-12
$ openclaw doctor --fix
This will:
adp-openclaw and qwen-portal-auth if those packages aren't installed)allowFrom: ["*"] to channels that need it--fix modifies your openclaw.json. Always back up first. If the fix breaks things, restore the backup.
$ gateway restart
$ sleep 10
$ cron list
{"jobs": [...] }
This is nuclear option. If your jobs.json is older than 7 days, the schema may have changed. Delete and rebuild from scratch using cron add for each job.
$ rm /root/.openclaw/cron/jobs.json
$ gateway restart
$ sleep 5
$ cron add --job '{...}' # rebuild manually
/root/.openclaw/cron/jobs.json — On-disk source of truth. Should have enabled jobs./root/.openclaw/cron/jobs-state.json — Last run history. Check if jobs are still scheduled. If lastRunAtMs is from hours/days ago, the scheduler isn't running./root/.openclaw/logs/stability/ — Failed startup logs. The directory has files named openclaw-stability-*.json with one per failed startup. The error.message field tells you exactly what blocked startup.channels.wecom: unknown channel id.doctor --fix.The Gateway reload issue is still unresolved. The blog pipeline ran for years on this stack and one bad config entry broke it. The fix would be OpenClaw making the cron loader more resilient to channel config failures, but that's a vendor-level change.
"Disk has the data" and "Gateway is running" are both necessary but neither is sufficient for cron jobs to fire. The third leg is "the startup validation passed AND the cron module reached the disk-loading step". A single broken channel config in openclaw.json can silently block the entire cron pipeline without leaving any error in the cron subsystem itself.
When debugging OpenClaw cron, check three things in this order:
stability/ for failed startups.cron status jobs count vs file size.jobs-state.json for nextRunAtMs in the future.If 1 fails: fix openclaw.json with doctor --fix.
If 2 fails with 1 passing: check schema compatibility, possibly rebuild jobs.json.
If 3 fails with 1 and 2 passing: check the schedule expression and time zone.
Three layers, three different fixes. Don't give up after the first layer.
Until OpenClaw ships a fix, I'm running a manual daily 8AM review:
#!/bin/bash
# cron_health.sh
EXPECTED=$(yq '.jobs[].name' /root/.openclaw/cron/jobs.json | wc -l)
ACTUAL=$(openclaw cron list 2>/dev/null | jq '.total')
if [ "$EXPECTED" != "$ACTUAL" ]; then
echo "ALERT: cron has $ACTUAL jobs, expected $EXPECTED"
# 手动补跑当天的发布位
fi
This is a band-aid. The real fix is a doctor-driven reload trigger. But until then, manual morning review beats silent failures.
📌 This article was AI-assisted generated and human-reviewed | TechPassive — An AI-driven content testing site focused on real tool reviews
These are carefully selected tools. Using our affiliate links supports us to keep producing quality content: