> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wednesdayai.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Automation Troubleshooting

# Automation troubleshooting

Use this page for scheduler and delivery issues (`cron` + `heartbeat`).

## Command ladder

```bash theme={"dark"}
openclaw status
openclaw gateway status
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
```

Then run automation checks:

```bash theme={"dark"}
openclaw cron status
openclaw cron list
openclaw system heartbeat last
```

## Cron not firing

```bash theme={"dark"}
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
openclaw logs --follow
```

Good output looks like:

* `cron status` reports enabled and a future `nextWakeAtMs`.
* Job is enabled and has a valid schedule/timezone.
* `cron runs` shows `ok` or explicit skip reason.

Common signatures:

* `cron: scheduler disabled; jobs will not run automatically` → cron disabled in config/env.
* `cron: timer tick failed` → scheduler tick crashed; inspect surrounding stack/log context.
* `reason: not-due` in run output → `openclaw cron run <jobId> --due` or Web UI **Run if due** was used and the job is not due yet.
* `main job requires payload.kind="systemEvent"` → the job is stored as a main-session job with an incompatible payload. Use the Web UI **Timeline message** or **Check first, then nudge** job type, or recreate the job with a matching `sessionTarget`/`payload.kind`.
* `isolated job requires payload.kind=agentTurn` → the job is stored as an isolated job without an assistant-task payload. Use the Web UI **Assistant task** job type or recreate the job with `--session isolated --message ...`.
* `wake-gate-empty` → the wake-gate check ran and found no work. This is a valid skipped result, not a scheduler failure.
* `already running` or `already-running` → the previous run or target lane is still active. Wait for it to finish, then inspect `openclaw cron runs --id <jobId> --limit 20`.

## Cron fired but no delivery

```bash theme={"dark"}
openclaw cron runs --id <jobId> --limit 20
openclaw cron list
openclaw channels status --probe
openclaw logs --follow
```

Good output looks like:

* Run status is `ok`.
* Delivery mode/target are set for isolated jobs.
* Channel probe reports target channel connected.

Common signatures:

* Run succeeded but delivery mode is `none` → no external message is expected.
* Delivery target missing/invalid (`channel`/`to`) → run may succeed internally but skip outbound.
* Channel auth errors (`unauthorized`, `missing_scope`, `Forbidden`) → delivery blocked by channel credentials/permissions.
* Web UI shows `Not delivered` on an internal run → delivery was not requested. Set result delivery to `announce` or `webhook` when you expect an outbound message.

## Heartbeat suppressed or skipped

```bash theme={"dark"}
openclaw system heartbeat last
openclaw logs --follow
openclaw config get agents.defaults.heartbeat
openclaw channels status --probe
```

Good output looks like:

* Heartbeat enabled with non-zero interval.
* Last heartbeat result is `ran` (or skip reason is understood).

Common signatures:

* `heartbeat skipped` with `reason=quiet-hours` → outside `activeHours`.
* `requests-in-flight` → main lane busy; heartbeat deferred.
* `empty-heartbeat-file` → interval heartbeat skipped because `HEARTBEAT.md` has no actionable content and no tagged cron event is queued.
* `alerts-disabled` → visibility settings suppress outbound heartbeat messages.

## Timezone and activeHours gotchas

```bash theme={"dark"}
openclaw config get agents.defaults.heartbeat.activeHours
openclaw config get agents.defaults.heartbeat.activeHours.timezone
openclaw config get agents.defaults.userTimezone || echo "agents.defaults.userTimezone not set"
openclaw cron list
openclaw logs --follow
```

Quick rules:

* `Config path not found: agents.defaults.userTimezone` means the key is unset; heartbeat falls back to host timezone (or `activeHours.timezone` if set).
* Cron without `--tz` uses gateway host timezone.
* Heartbeat `activeHours` uses configured timezone resolution (`user`, `local`, or explicit IANA tz).
* ISO timestamps without timezone are treated as UTC for cron `at` schedules.

Common signatures:

* Jobs run at the wrong wall-clock time after host timezone changes.
* Heartbeat always skipped during your daytime because `activeHours.timezone` is wrong.

Related:

* [/automation/cron-jobs](/automation/cron-jobs)
* [/gateway/heartbeat](/gateway/heartbeat)
* [/automation/cron-vs-heartbeat](/automation/cron-vs-heartbeat)
* [/concepts/timezone](/concepts/timezone)
