Skip to content

[Bug]: sessionTarget="main" cron jobs silently skipped when heartbeat.every="0m" #46046

Description

@vimholic

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

  1. Create a cron job with sessionTarget: "main" and wakeMode: "now"
  2. Set agents.defaults.heartbeat.every to "0m" (e.g. via openclaw config set agents.defaults.heartbeat.every "0m")
  3. Restart gateway
  4. 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 1wakeMode: "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"

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions