Summary
sessionTarget: "main" cron jobs report status: "skipped", error: "disabled" when agents.defaults.heartbeat.every is set to "0m", even though the jobs fire at the correct scheduled time.
Environment
- OpenClaw: v2026.3.12 (confirmed in source)
- Cron job config:
sessionTarget: "main", wakeMode: "now", payload.kind: "systemEvent"
Steps to Reproduce
- Create a cron job with
sessionTarget: "main" and wakeMode: "now"
- Set
agents.defaults.heartbeat.every to "0m" (e.g. via openclaw config set agents.defaults.heartbeat.every "0m")
- Restart gateway
- Wait for the cron job to fire at its scheduled time
Result: Job is recorded as status: "skipped", error: "disabled" in the run log. The system event is never delivered to the main agent session.
Expected: Cron jobs should execute (or at minimum, queue the event) regardless of the heartbeat interval setting. Heartbeat configuration controls periodic background check-ins, not scheduled cron delivery.
Root Cause
Two separate code paths both block on heartbeat.every = "0m":
Path 1 — wakeMode: "now" calls runHeartbeatOnce() (from health-BnAAsjEg.js):
// health-BnAAsjEg.js:416
if (!resolveHeartbeatIntervalMs(cfg, void 0, heartbeat)) return {
status: "skipped",
reason: "disabled" // ← "0m" → 0ms → falsy → blocked here
};
The cron dispatcher merges baseHeartbeat (which includes every: "0m") into the override passed to runHeartbeatOnce, so the interval check always fails.
Path 2 — The scheduler's updateConfig skips registering agents with interval=0:
// health-BnAAsjEg.js:807-808
const intervalMs = resolveHeartbeatIntervalMs(cfg, void 0, agent.heartbeat);
if (!intervalMs) continue; // agent never added to state.agents
So requestHeartbeatNow() (the fallback for non-"now" wakeMode) also fails:
const targetAgent = state.agents.get(targetAgentId);
if (!targetAgent) return { status: "skipped", reason: "disabled" };
Both paths fail. There is no workaround at the cron job level — the only fix is to not set heartbeat.every = "0m".
Why This Matters
heartbeat.every = "0m" is the documented way to disable periodic heartbeats (e.g. on weekends). But this inadvertently disables all sessionTarget: "main" cron jobs — even though cron scheduling and heartbeat are conceptually independent features.
The coupling happens because runHeartbeatOnce (designed for periodic scheduling) is reused as the "wake the main session now" mechanism for cron, applying the same interval validity checks in a context where they don't belong.
Suggested Fix
In the cron dispatcher (gateway-cli.js, runMainSessionCronJob), when calling runHeartbeatOnce for wakeMode: "now", pass a non-zero overrideEvery (e.g. "1ms") to bypass the interval check:
heartbeatResult = await state.deps.runHeartbeatOnce({
reason,
agentId: job.agentId,
sessionKey: targetMainSessionKey,
heartbeat: { target: "last", every: "1ms" } // override: bypass interval check
});
Or, add a dedicated forceWakeMainSession() path that enqueues the system event and directly triggers agent processing without going through the heartbeat interval validation.
Workaround
Set heartbeat.every to a non-zero value even when periodic heartbeats are not desired (e.g. "24h"). This allows state.agents registration to succeed and the interval check to pass, while keeping heartbeat activity minimal.
# Instead of "0m", use a large interval
openclaw config set agents.defaults.heartbeat.every "24h"
Summary
sessionTarget: "main"cron jobs reportstatus: "skipped",error: "disabled"whenagents.defaults.heartbeat.everyis set to"0m", even though the jobs fire at the correct scheduled time.Environment
sessionTarget: "main",wakeMode: "now",payload.kind: "systemEvent"Steps to Reproduce
sessionTarget: "main"andwakeMode: "now"agents.defaults.heartbeat.everyto"0m"(e.g. viaopenclaw config set agents.defaults.heartbeat.every "0m")Result: Job is recorded as
status: "skipped",error: "disabled"in the run log. The system event is never delivered to the main agent session.Expected: Cron jobs should execute (or at minimum, queue the event) regardless of the heartbeat interval setting. Heartbeat configuration controls periodic background check-ins, not scheduled cron delivery.
Root Cause
Two separate code paths both block on
heartbeat.every = "0m":Path 1 —
wakeMode: "now"callsrunHeartbeatOnce()(fromhealth-BnAAsjEg.js):The cron dispatcher merges
baseHeartbeat(which includesevery: "0m") into the override passed torunHeartbeatOnce, so the interval check always fails.Path 2 — The scheduler's
updateConfigskips registering agents with interval=0:So
requestHeartbeatNow()(the fallback for non-"now"wakeMode) also fails:Both paths fail. There is no workaround at the cron job level — the only fix is to not set
heartbeat.every = "0m".Why This Matters
heartbeat.every = "0m"is the documented way to disable periodic heartbeats (e.g. on weekends). But this inadvertently disables allsessionTarget: "main"cron jobs — even though cron scheduling and heartbeat are conceptually independent features.The coupling happens because
runHeartbeatOnce(designed for periodic scheduling) is reused as the "wake the main session now" mechanism for cron, applying the same interval validity checks in a context where they don't belong.Suggested Fix
In the cron dispatcher (
gateway-cli.js,runMainSessionCronJob), when callingrunHeartbeatOnceforwakeMode: "now", pass a non-zerooverrideEvery(e.g."1ms") to bypass the interval check:Or, add a dedicated
forceWakeMainSession()path that enqueues the system event and directly triggers agent processing without going through the heartbeat interval validation.Workaround
Set
heartbeat.everyto a non-zero value even when periodic heartbeats are not desired (e.g."24h"). This allowsstate.agentsregistration to succeed and the interval check to pass, while keeping heartbeat activity minimal.