Skip to content

Scheduled Tasks Automations: cron, reminders, and Webhook triggers ​

The comparison table in the previous lesson broke "have Claw get things done while you're not watching" into several mechanisms; this lesson takes apart and explains thoroughly the most frequently used one of them, OpenClaw scheduled tasks (officially named Automations). What is it? In one sentence: OpenClaw's built-in scheduler—jobs are persisted in the shared SQLite state store, the Gateway wakes the agent at the appointed time, and results can be delivered to a chat channel, a webhook endpoint, or nowhere at all.

The command-line entry point is openclaw automations, and openclaw cron is an alias for the same set of commands—don't panic if an older tutorial writes openclaw cron, it's the same thing. All examples are based on OpenClaw v2026.10.9.

What you'll be able to do after this lesson ​

  • Use --at to create one-time reminders, and --cron / --every to create recurring tasks; they're visible in openclaw automations list and run automatically at the appointed time
  • Deliver task results to a chat channel you specify (--announce --channel --to) or a webhook endpoint (--webhook)
  • Manage created tasks: check run history, temporarily disable, change the prompt, force an immediate run
  • Turn on the inbound Webhook so external services (CI, monitoring, scripts) can proactively wake up Claw via HTTP
  • Avoid two classic pitfalls: the cron day/weekday OR trap, and three mutually unrelated time zone settings

Your current predicament ​

  • Every morning when you wake up, you have to go through a dozen websites and group messages yourself to piece together "what happened last night"
  • "Remind me to send the weekly report before leaving work" still relies on your own memory; the AI assistant is only useful when asked, and it stalls the moment you step away
  • You want the AI to take a look at the logs as soon as CI finishes, but you don't know how to "feed" external events to it

When to use this technique ​

When you need to:

  • Work on a fixed rhythm: summarize news at 7 every morning, remind about the weekly report every Friday, check queue depth every 15 minutes
  • One-time reminders: remind me in 20 minutes, have me check a checklist next Monday morning
  • External event triggers: wake up Claw to work when code is pushed, a build finishes, or a script completes

And you don't want to:

  • Build the whole crontab + scripts + notification pipeline setup yourself
  • Stare at the chat window for 24 hours waiting for messages

💡 Not sure which mechanism to use?

Go back to the comparison table in the previous lesson: scheduled = Automations (this lesson); precise one-time reminders = Automations with --at; external triggers = Webhooks (step 4 of this lesson); session lifecycle scripts = Hooks; persistent instructions written into the workspace = Standing Orders. See Efficient Workflows Stage Overview.

🎒 Preparation before starting ​

  • You've finished Efficient Workflows Stage Overview: the Gateway is running and at least one model is connected (/model status can report the current model)
  • You have terminal access on the machine running the Gateway—the automations commands need a running Gateway, and all mutation operations (add/create, edit, remove, run) require operator.admin permission
  • Want results sent to Feishu, WeCom, Telegram, or Slack? First connect at least one channel in Scenario Walkthroughs; you can still learn without one, using --no-deliver or just looking at run history
  • A cup of water ☕—this lesson is all short commands, ready to run just by copying

Core idea ​

A scheduled task (job) = scheduling (when to run) + payload (what to run) + delivery (where the result goes). Each of the three dimensions has a few options—get familiar with them first, and we'll use them step by step below:

Five kinds of scheduling (when to trigger):

TypeCLI argumentMeaning
at--atOne-time point in time: an ISO 8601 timestamp, or a relative time like 20m
every--everyFixed interval: 10m, 1h, 1d
cron--cron5/6-field cron expression, with a configurable --tz timezone
on-exit--on-exitTriggers once when the watched command exits
stream--stream-commandBatches output lines from a long-running command to trigger

Four payload types (what to run):

PayloadCLI argumentMeaning
System event--system-eventTriggers a normal turn in the owning agent's main session (first choice for reminders)
Agent message--messageA full model turn, letting Claw actually do the work
Command--commandRuns a shell command on the Gateway host, without calling the model
Script--scriptHeadless code-mode script, able to use the owning agent's tools

Four execution sessions (where it runs): --session main (the main session, good for reminders), isolated (a brand-new session each time, good for recurring report tasks), current (bound to the conversation that created it), session:<id> (a persistent session that accumulates context across runs).

Three delivery modes (where results go): --announce (sent into a chat channel), --webhook (POSTed to an HTTP endpoint), --no-deliver (nothing sent, only a run record is kept).

💡 Two aliases—don't get confused

openclaw cron is an alias of openclaw automations; automations create is an alias of automations add. New tasks support positional arguments: schedule first, prompt second, e.g. openclaw automations create "0 7 * * *" "Summarize overnight updates.". Natural-language scheduling ("every day at 9am") is not supported—just write the cron honestly.

Follow along ​

Step 1: Create your first OpenClaw scheduled task (reminds you after 20 minutes) ​

Why: A one-off reminder is the smallest scheduled task, set up in 30 seconds, and it confirms the whole chain (create → store → fire on time) works.

bash
openclaw automations add \
  --name "Calendar check" \
  --at "20m" \
  --session main \
  --system-event "Check the calendar for upcoming events."

Match up each argument: --at "20m" means run once after 20 minutes (an ISO timestamp also works); --session main fires in the main session; --system-event is the reminder content.

You should see: The command executes successfully with no errors. Then verify it was stored:

bash
openclaw automations list

You should see: A task named Calendar check appears in the list along with its job id—write that id down, you'll use it throughout.

  • Quick check: Calendar check is visible in openclaw automations list

You can also write an absolute time directly. The following is adapted from the official quick-start example (only the deprecated --wake now was removed); --delete-after-run means the task is deleted automatically after a successful run, leaving no clutter:

bash
openclaw automations create "2027-02-01T16:00:00Z" \
  --name "Reminder" \
  --session main \
  --system-event "Reminder: check the automations docs draft" \
  --delete-after-run

💡 The --wake now in old tutorials online is deprecated

--wake now|next-heartbeat This argument is now just a compatibility field that is "accepted but does nothing"—tasks fire automatically at the scheduled time via normal session execution, with no extra wake step needed. Scheduling behavior is determined entirely by the schedule arguments; just delete it when you see it.

Step 2: Create a cron recurring task that delivers results to a chat channel ​

Why: A one-off reminder can only ping you once; what really saves time are recurring tasks like "summarize and send it to me every morning"—cron sets the rhythm, and the delivery arguments decide where the results land.

bash
openclaw automations create "0 7 * * *" \
  "Summarize overnight updates. 用中文回复;链接、代码和产品名保持原样。" \
  --name "Morning brief" \
  --tz "Asia/Shanghai" \
  --session isolated \
  --announce \
  --channel slack \
  --to "channel:C1234567890"

Break it down line by line:

ArgumentPurpose
"0 7 * * *"Positional argument one: the cron expression, every day at 07:00
"Summarize overnight updates. …"Positional argument two: the task prompt. The task does not infer the reply language from the channel or message history; to get Chinese replies, put it in the prompt
--tz "Asia/Shanghai"Timezone for cron evaluation. If omitted, the Gateway host's timezone is used
--session isolatedStarts a brand-new session each run, keeping the main conversation clean
--announce --channel slack --to "channel:C1234567890"Delivers results to a Slack channel. Slack/Discord/Mattermost use the channel:<id> and user:<id> prefixes; Telegram groups use numeric ids starting with -100…

You should see: Created successfully. add/create, list, and show all preview the parsed delivery route—if the destination can't be resolved, they'll tell you why right there, so you don't have to wait until the next morning to discover nothing arrived.

  • Quick check: openclaw automations show <job-id> shows the parsed delivery route

Three additional notes:

  • Target formats vary by channel: For how to connect and write targets for domestic IMs like Feishu and WeCom, see Scenario Walkthroughs (Feishu Integration, WeCom Integration). On a host with multiple channels configured, an isolated task must provide --channel (unless --to carries a provider prefix or a session route is retained).
  • You can still learn without a connected channel: An isolated task created via the CLI needs one of three things—a resolvable channel route, webhook delivery, or --no-deliver; add --no-deliver when practicing, and results go only into the run history.
  • Don't let it spam the chat: For scheduled check tasks, agree in the prompt that "when nothing happened, only reply NO_REPLY"—when the entire final reply is uppercase NO_REPLY, delivery is silently skipped, while the execution record is still written to history as usual.

Want to go with a webhook instead of a chat channel? Swap the delivery parameter to --webhook (it is mutually exclusive with --announce/--channel/--to and cannot be used together with them):

bash
openclaw automations create "0 18 * * 1-5" \
  "Summarize today's deploys as JSON." \
  --name "Deploy digest" \
  --webhook "https://example.invalid/openclaw/cron"

Step 3: Managing scheduled tasks — run history, disabling, and running immediately ​

Why: Creating a task is not a one-and-done deal — editing the prompt, temporarily disabling it, or checking "did it actually run" are all everyday operations. They all live in the single openclaw automations command family:

bash
openclaw automations list                  # 启用中的任务
openclaw automations list --all            # 含已禁用的任务
openclaw automations get <job-id>          # 单个任务的完整 JSON
openclaw automations show <job-id>         # 单个任务 + 解析后的投递路由
openclaw automations runs <job-id> --limit 50   # 运行历史
openclaw automations edit <job-id> --message "Updated prompt"   # 改提示词
openclaw automations disable <job-id>      # 停用(不删除)
openclaw automations enable <job-id>       # 重新启用
openclaw automations run <job-id>          # 立即强制运行
openclaw automations remove <job-id>       # 删除

Don't want to wait 20 minutes to verify the reminder from Step 1? A forced run takes effect immediately:

bash
openclaw automations run <job-id>

You should see: output like { ok: true, enqueued: true, runId: "…" } — run returns as soon as the run is enqueued; use runId to check the result of this particular execution:

bash
openclaw automations runs <job-id> --run-id <run-id>

You should see: the record of this run, with status being ok, error, or skipped (the payload execution status), plus the overall completion status completionStatus (succeeded / failed / unknown).

  • Quick check: the record of the forced run you just made is visible in openclaw automations runs <job-id>

Three behaviors worth remembering:

  • Manual runs don't consume the schedule: a forced run does not consume the scheduled task's next slot; manually running a disabled task once also does not automatically resume its scheduling — use enable to resume it.
  • Automatic backoff on failure: recurring tasks that fail consecutively retry with 30s → 1m → 5m → 15m → 60m backoff and reset after the next success; a time-based recurring task that fails 10 times in a row is automatically disabled (use list --all to find it, then enable after fixing the cause).
  • Change the model with --model: automations edit <job-id> --model "opus" --thinking high can switch the model/thinking level for a single task; if the model is not allowed, the task fails validation outright instead of silently falling back to the default model.

Step 4: Enable inbound webhooks so external services can wake Claw ​

Why: the first three trigger types are all time-driven; for external events like "have Claw look at the logs as soon as CI finishes," you need an inbound webhook — the opposite direction — where an external service calls OpenClaw over HTTP. It is disabled by default; enable it in three steps.

1. Add it to the config. Merge the following into the Gateway config file (~/.openclaw/openclaw.json), replacing token with a long random string — a dedicated token; do not reuse the Gateway auth token or a password; and replace main with the agent id you want to wake:

json5
{
  hooks: {
    enabled: true,
    token: "<long-random-hook-token>",
    path: "/hooks",
    allowedAgentIds: ["main"],
    allowRequestSessionKey: false,
  },
}

2. Validate and restart (if the Gateway is running in the foreground, restart that foreground process):

bash
openclaw config validate
openclaw gateway restart
openclaw logs --follow

You should see: validation passes with no errors, and the Gateway starts streaming logs after the restart.

3. Smoke test. In another terminal, send a harmless test to the local Gateway (replace the token, agent id, and port with your own config; the default port is 18789):

bash
curl --include http://127.0.0.1:18789/hooks/agent \
  -H 'Authorization: Bearer <long-random-hook-token>' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: webhook-smoke-001' \
  --data '{"message":"Summarize this test event: the sample import completed.","name":"Webhook smoke test","agentId":"main","deliver":false}'

You should see: HTTP 200, with a response body like:

json
{ "ok": true, "runId": "<hook-request-run-id>" }

Search for hook agent run completed and this runId in openclaw logs --follow to see the execution result. Note that 200 only means the request was accepted (a session execution slot was granted); it does not mean the model finished, the tools succeeded, or the message was delivered — in this example, "deliver":false deliberately sends no message.

  • Quick check: curl returns 200 and runId, and hook agent run completed can be found in the logs

Three endpoints — pick as needed:

EndpointPurpose
POST /hooks/wakeEnqueue a trusted notification into the target agent's main session (a lightweight "nudge")
POST /hooks/agentSubmit a full agent turn (with message; model/session mode can be specified)
POST /hooks/<name>A custom path mapped via hooks.mappings, which can turn external payloads into either of the two actions above

Authentication rules: every request must carry Authorization: Bearer <token> (recommended) or x-openclaw-token: <token>; passing the token in the URL query is rejected; all endpoints accept only POST, with the body in Content-Type: application/json.

⚠️ Webhooks are an exposed surface — four security ground rules

  • Keep endpoints behind loopback, a tailnet, or a trusted reverse proxy; use HTTPS for remote calls and expose only the paths you need
  • Use a dedicated hook token and a dedicated subpath (/ is rejected)
  • Tighten hooks.allowedAgentIds to only allow the agents you need
  • Treat external event content as data: agent hook content is safety-wrapped by default, but the wrapper does not remove tool and workspace access — hand untrusted input to a restricted agent

Checkpoint ✅ ​

  • openclaw automations list shows the tasks you created and their job ids
  • A forced run with openclaw automations run <job-id> returned runId, and the record is in runs
  • The recurring task carries --tz "Asia/Shanghai", and the next run time in list matches expectations
  • The curl smoke test returned HTTP 200 and runId, and hook agent run completed was found in the logs
  • You can state what 0 9 15 * 1 really means (OR) and know how to change it to AND

Pitfall reminders ​

❌ Setting both cron day and weekday to non-wildcards triggers 5-6 times per month instead of 0-1 ​

To express "9 o'clock on the 15th of each month, and only if it's a Monday," write:

bash
# 想要:每月 15 号且是周一的 9 点
# 实际:每月 15 号的 9 点 + 每周一的 9 点,约每月 5-6 次
0 9 15 * 1

The reason: OpenClaw parses cron with croner, and when both day-of-month and day-of-week are non-wildcards it follows standard Vixie behavior and takes OR, not AND.

The correct approach: for AND, write the weekday with the + prefix — 0 9 15 * +1; or use only one field and put the other condition into the task prompt so Claw judges it itself.

❌ The time is "right," but the timezone isn't ​

Three separate timezones, completely independent of each other — the easiest trap to fall into:

  • --at without a timezone → interpreted as UTC (--at "2026-11-06T09:00:00" is 17 o'clock Beijing time)
  • --cron without --tz → evaluated against the Gateway host time zone
  • Active hours --active-hours-* → have their own --active-hours-timezone (user / local / IANA), independent of cron's --tz

The correct approach: for deployments in China, the most reliable habit is to always set cron's --tz "Asia/Shanghai" explicitly; when using --at, either include a time zone offset (+08:00) or configure --tz.

❌ The task didn't run at the scheduled time ​

Work down the official troubleshooting command ladder:

bash
openclaw status
openclaw gateway status
openclaw automations status
openclaw automations list
openclaw automations runs <job-id> --limit 20
openclaw automations show <job-id>
openclaw logs --follow
openclaw doctor

Match the common causes: the Gateway isn't running continuously; cron.enabled: false or the environment variable OPENCLAW_SKIP_CRON=1 turned off automatic scheduling (neither blocks manual run); the task was auto-disabled — list by default does not show disabled tasks, so you need list --all to find them, and after fixing the cause, enable; finally, double-check whether you misread --tz versus the host time zone.

❌ Webhook returns 200, but no message appears in chat ​

200 only means the request was accepted. Troubleshooting order:

  • In the smoke test, "deliver":false intentionally does not send a message by design — this is intentional, not a failure
  • If you want execution and delivery results in the same request: add "waitForCompletion": true to the payload, and the completion in the response contains status, delivered, deliverySuppressionReason
  • Direct channel delivery must provide both channel and to; providing only one gets rejected with 400 before dispatch
  • If the log shows status=ok but with deliveryError = execution succeeded but delivery failed; the two must be examined separately

Advanced techniques (optional) ​

Conditional triggers: check on a schedule, wake up only when something changes. Attach a --trigger-script ./watch-pr-ci.js to an every/cron/stream schedule; the script returns { fire, message?, state? } — with fire: false, it quietly skips and sends no model turn. State is capped at 16 KB, with a 30-second budget per evaluation and at most 5 tool calls. Note: a forced run will bypass the conditional gate and execute directly; run --due respects the gate. On security, be clear: conditional scripts run unattended by default with the owning agent's full tool policy (including exec); set cron.triggers.enabled: false when you need a hard stop. For a hands-on build, see Web monitoring: get notified the moment a page changes.

/loop: create a loop with a single sentence in chat. The owner-only /loop [interval] <prompt> creates a recurring task in the current conversation — /loop 5m check deploy status sets a fixed cadence; without an interval it self-paces (between 1 minute and 1 hour). Use /loop status to view, /loop stop [name] to stop. For command details see Chat commands crash course.

Monitoring strategy: don't let background tasks wake you at midnight. Add active hours and yield switches to check-type tasks:

bash
openclaw automations add \
  --name "Project monitor" \
  --every 30m \
  --session isolated \
  --message "Check the job scratch for newly blocked work. Report changes that need attention; otherwise reply NO_REPLY." \
  --active-hours-start 08:00 \
  --active-hours-end 22:00 \
  --active-hours-timezone user \
  --idle-only \
  --skip-if-scratch-empty \
  --no-deliver

--idle-only lets foreground work take priority, and --skip-if-scratch-empty skips the model turn when the task's scratchpad is explicitly empty.

Scheduled tasks + local models. Before an isolated task starts, the scheduler first probes whether the local model endpoint is reachable (the Ollama endpoint corresponds to /api/tags, and OpenAI-compatible endpoints such as vLLM / SGLang / LM Studio correspond to /models) — the probe is only a TCP connectivity check and sends no HTTP request. If the probe fails, this run is recorded as skipped and retried at the next slot (results are cached per endpoint for 5 minutes, so there's no rapid-fire hammering). For local model setup see Local models: Ollama, vLLM and ds4.

A few master switches. cron.enabled: false or OPENCLAW_SKIP_CRON=1: stop all automatic scheduling (manual run is unaffected). cron.skipMissedJobs: true: skip recurring slots missed while the Gateway was down, avoiding a burst of backfilled stale reminders after restart (backfill is the default; one-off at tasks are unaffected by this switch). Startup environment variable OPENCLAW_DISABLE_CRON_HOST_EXECUTION=1: hard-disable command/script payloads, conditional triggers, on-exit, and Gateway-hosted streams — after changing the startup environment, restart the Gateway for it to take effect.

Lesson summary ​

✅ OpenClaw scheduled tasks = the built-in Automations scheduler: openclaw automations (openclaw cron is an alias); tasks persist in the shared SQLite state store and are woken by the Gateway at the scheduled time.

✅ One task, three dimensions: schedule (--at / --every / --cron --tz / --on-exit / --stream-command) × payload (--system-event / --message / --command / --script) × delivery (--announce --channel --to / --webhook / --no-deliver).

✅ Execution session, pick one of four: main (reminders), isolated (report-type, fresh every time), current (bound to the creating conversation), session:<id> (accumulates across runs).

✅ Inbound webhooks are disabled by default: enable with hooks.enabled plus a dedicated token; three endpoints — /hooks/wake, /hooks/agent, /hooks/<name> — accept only POST + Bearer auth, and 200 only means the request was accepted.

✅ Two classic pitfalls: when both the cron day and weekday fields are non-wildcard, it's an OR (use the + prefix for AND); --at without a time zone uses UTC, cron without --tz uses the host time zone, and active hours have yet another time zone — three unrelated systems.

Key config fieldRecommended valueNotes
--tz"Asia/Shanghai" (China)time zone for cron evaluation; also applies to --at times without an offset
--sessionisolated (reports) / main (reminders)isolated starts a fresh session each time, without polluting the main conversation
--announce --channel --tochannel id + targetSlack/Discord use channel:<id>; on multi-channel hosts, --channel is mandatory
hooks.enabled / hooks.tokentrue / a long random dedicated tokenInbound webhook toggle and authentication; do not reuse Gateway authentication
cron.enabledtruefalse or OPENCLAW_SKIP_CRON=1 stops automatic scheduling
cron.triggers.enabledtrue (default)false Hard-stop condition scripts, script load, and stream scheduling

CLI quick reference: openclaw automations list / get / show / runs / edit / enable / disable / run / remove, add --help to see all parameters. For the full command table, see CLI Commands and Configuration Quick Reference.

Next Lesson Preview ​

Scheduled tasks solve "when to do it"; the next lesson Browser Control: Let Claw Browse and Operate the Web for You gives it a pair of hands for the web, solving "with what to do it"—combine the two lessons, and the morning brief and monitoring become a matter of copying straight over.

You will learn:

  • web_search / x_search / web_fetch a lightweight trio: search for information, extract main content
  • An isolated openclaw managed browser: click buttons, fill in forms, take screenshots, without touching your own browser
  • A user profile that reuses an already-logged-in Chrome session
  • A combo preview: scheduling + web browsing = Daily Morning Brief, Web Page Monitoring

Related Tutorials ​