OpenClaw Cron CLI Reference: Schedule and Manage Background Jobs

This page covers the openclaw cron CLI for scheduling and managing cron jobs on the Gateway scheduler. It is intended for administrators who need to create, update, remove, or run background jobs.

Read this when

  • You want scheduled jobs and wakeups
  • You are debugging cron execution and logs

openclaw cron

Schedule and manage cron jobs for the Gateway scheduler.

Tip

Run openclaw cron --help to see the full command reference. The conceptual guide is available at Cron jobs.

Note

Any change to a cron job (add/create, update/edit, remove, run) needs operator.admin. Commands run in the Gateway process directly, not through an agent tools.exec tool call; tools.exec.* and exec approvals still apply to exec tools visible to the model.

Create jobs quickly

openclaw cron create is a shorthand for openclaw cron add. When creating a new job, place the schedule first, then the prompt:

openclaw cron create "0 7 * * *" \
  "Summarize overnight updates." \
  --name "Morning brief" \
  --agent ops

If the job should POST the finished payload rather than deliver it to a chat target, use --webhook <url>:

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

For deterministic shell-style jobs that execute inside OpenClaw cron without launching a separate agent or model run, use --command:

openclaw cron create "*/15 * * * *" \
  --name "Queue depth probe" \
  --command "scripts/check-queue.sh" \
  --command-cwd "/srv/app" \
  --announce \
  --channel telegram \
  --to "-1001234567890"

--command <shell> holds argv: ["sh", "-lc", <shell>]. For precise argv execution, use --command-argv '["node","scripts/report.mjs"]'. Command jobs capture stdout and stderr, log normal cron history, and send output through the same announce, webhook, or none delivery modes as isolated jobs. A command that outputs only NO_REPLY is not shown.

Sessions

--session takes main, isolated, current, or session:<id>.

Session keys

  • main attaches to the agent's primary session.
  • isolated generates a new transcript and session id for every run.
  • current attaches to whichever session is active when the job is created.
  • session:<id> locks to a specific persistent session key.

Isolated session semantics

Isolated runs clear the ambient conversation context. Channel and group routing, send and queue policy, elevation, origin, and ACP runtime binding are all reset for the new run. Safe preferences and explicit user model or auth overrides can persist across runs.

Delivery

openclaw cron list and openclaw cron show <job-id> show the resolved delivery route before it is used. For channel: "last", the preview indicates whether the route came from the main or current session, or will fail closed.

Unresolved announce channels can be disambiguated with provider-prefixed targets. For instance, to: "telegram:123" picks Telegram when delivery.channel is missing or last. Only prefixes that the loaded plugin advertises act as provider selectors. If delivery.channel is explicit, the prefix must match that channel; channel: "whatsapp" with to: "telegram:123" is not allowed. Service prefixes like imessage: and sms: remain channel-owned target syntax.

Note

Isolated cron add jobs use --announce delivery by default. To keep output internal, use --no-deliver. --deliver is a deprecated alias for --announce.

Delivery ownership

Isolated cron chat delivery is shared between the agent and the runner:

  • When a chat route exists, the agent can use the message tool to send directly.
  • The announce flag delivers the final reply as a fallback only if the agent did not send directly to the resolved target.
  • webhook sends the completed payload to a specified URL.
  • none turns off runner fallback delivery.

To configure webhook delivery, use either cron add|create --webhook <url> or cron edit <job-id> --webhook <url>. Do not use --webhook alongside chat delivery flags like --announce, --no-deliver, --channel, --to, --thread-id, or --account.

Individual delivery routing fields can be cleared with cron edit <job-id> using --clear-channel, --clear-to, --clear-thread-id, and --clear-account (each is disallowed when paired with its corresponding set flag). Unlike --no-deliver, which only deactivates runner fallback delivery, these remove the stored field so the job resolves that part of its route from defaults once more.

Runner fallback delivery for the final reply is handled by --announce. --no-deliver deactivates that fallback but does not remove the agent's message tool when a chat route is available.

Reminders generated from an active chat keep the live chat delivery target for fallback announce delivery. Internal session keys might be lowercase; avoid using them as a reliable source for case-sensitive provider IDs, such as Matrix room IDs.

Failure delivery

Failure notifications follow this resolution order:

  1. delivery.failureDestination on the job.
  2. Global cron.failureDestination.
  3. The job's primary announce target (when neither of the above resolves to a concrete destination).

Note

Main-session jobs can only use delivery.failureDestination when the primary delivery mode is webhook. Isolated jobs accept it in all modes.

In isolated cron runs, run-level agent failures are treated as job errors even when no reply payload is produced, so model or provider failures still increase error counters and can trigger failure notifications.

Command cron jobs do not initiate an isolated agent turn. A zero exit code results in ok; a non-zero exit, signal, timeout, or no-output timeout results in error and can follow the same failure notification path.

If an isolated run times out before the first model request, openclaw cron show and openclaw cron runs contain a phase-specific error such as setup timed out before runner start or a stall message naming the last-known startup phase (for example context-engine). For CLI-backed providers, the pre-model watchdog remains active until the external CLI turn starts, so session lookup, hook, auth, prompt, and CLI setup stalls are reported as pre-model cron failures.

Scheduling

One-shot jobs

A one-shot run is scheduled by --at <datetime>. Datetimes without an offset are interpreted as UTC unless you also provide --tz <iana>, which reads the wall-clock time in the specified timezone.

Note

By default, one-shot jobs are deleted after a successful run. Use --keep-after-run to keep them.

Recurring jobs

Recurring jobs apply exponential retry backoff after consecutive errors: 30s, 1m, 5m, 15m, 60m. The schedule returns to normal following the next successful run.

Skipped runs are tracked separately from execution errors. They do not affect retry backoff, but openclaw cron edit <job-id> --failure-alert-include-skipped can enable failure alerts for repeated skipped-run notifications.

For isolated jobs targeting a local configured model provider (base URL on loopback, a private network, or .local), cron runs a lightweight provider preflight before starting the agent turn: api: "ollama" providers are probed at /api/tags; other local OpenAI-compatible providers (api: "openai-completions", e.g. vLLM, SGLang, LM Studio) are probed at /models. If the endpoint is unreachable, the run is recorded as skipped and retried on a later schedule; the reachability result is cached per endpoint for 5 minutes so many jobs against the same local server do not overload it with repeated probes.

Cron jobs, pending runtime state, and run history are stored in the shared SQLite state database. Legacy jobs.json, <name>-state.json, and runs/*.jsonl files are imported once and renamed with a .migrated suffix. After import, edit schedules with openclaw cron add|edit|remove instead of editing JSON files.

Manual runs

By default, openclaw cron run <job-id> force-runs and returns as soon as the manual run is queued. Successful responses include { ok: true, enqueued: true, runId }. Use the returned runId to inspect the later result:

openclaw cron run <job-id>
openclaw cron runs --id <job-id> --run-id <run-id>

Add --wait when a script should block until that exact queued run records a terminal status:

openclaw cron run <job-id> --wait --wait-timeout 10m --poll-interval 2s

When --wait is provided, the CLI first calls cron.run, then repeatedly checks cron.runs for the returned runId. The command only exits with code 0 once the run completes with status ok. A non-zero exit occurs if the run finishes with error or skipped, if the Gateway response lacks a runId, or if --wait-timeout times out (default 10m, polled every 2s by default). --poll-interval must be set to a value above zero.

Note

Use --due when you want the manual command to execute only if the job is currently due. If --due --wait does not queue a run, the command returns the standard non-run response instead of polling.

Models

cron add|edit --model <ref> specifies an allowed model for the job. cron add|edit --fallbacks <list> defines per-job fallback models, for instance --fallbacks openrouter/gpt-4.1-mini,openai/gpt-5; supply --fallbacks "" to enforce a strict run with no fallbacks. cron edit <job-id> --clear-fallbacks clears the per-job fallback override. cron edit <job-id> --clear-model removes the per-job model override, causing the job to follow the normal cron model-selection precedence (a stored cron-session override if one exists, otherwise the agent or default model); it cannot be used with --model. cron add|edit --thinking <level> sets a per-job thinking override; cron edit <job-id> --clear-thinking removes it so the job follows normal cron thinking precedence, and it cannot be combined with --thinking.

Warning

If the model is not permitted or cannot be resolved, cron fails the run with an explicit validation error rather than falling back to the job's agent or default model selection.

Cron --model functions as a job primary, not as a chat-session /model override. This means:

  • Configured model fallbacks still apply when the selected job model fails.
  • Per-job payload fallbacks replaces the configured fallback list when present.
  • An empty per-job fallback list (--fallbacks "" or fallbacks: [] in the job payload or API) makes the cron run strict.
  • When a job has --model but no fallback list is configured, OpenClaw passes an explicit empty fallback override so the agent primary is not appended as a hidden retry target.
  • Local-provider preflight checks walk configured fallbacks before marking a cron run skipped.

openclaw doctor reports jobs that already have payload.model set, including provider namespace counts and mismatches against agents.defaults.model. Use this check when auth, provider, or billing behavior differs between live chat and scheduled jobs.

Isolated cron model precedence

Isolated cron resolves the active model in this order:

  1. Gmail-hook override.
  2. Per-job --model.
  3. Stored cron-session model override (when the user selected one).
  4. Agent or default model selection.

Fast mode

Isolated cron fast mode follows the resolved live model selection. Model config params.fastMode applies by default, but a stored session fastMode override still takes precedence over config. When the resolved mode is auto, the cutoff uses the selected model's params.fastAutoOnSeconds value, defaulting to 60 seconds.

Live model switch retries

If an isolated run throws LiveSessionModelSwitchError, cron persists the switched provider and model (and the switched auth profile override when present) for the active run before retrying. The outer retry loop is limited to two switch retries after the initial attempt, then aborts instead of looping indefinitely.

Run output and denials

Stale acknowledgement suppression

Isolated cron suppresses stale acknowledgement-only replies. If the first result is only an interim status update and no descendant subagent run is responsible for the eventual answer, cron re-prompts once for the real result before delivery.

Silent token suppression

If an isolated cron run returns only the silent token (NO_REPLY or no_reply), cron suppresses both direct outbound delivery and the fallback queued summary path, so nothing is posted back to chat.

Structured denials

Isolated cron runs use structured execution-denial metadata from the embedded run (fatal exec-tool errors coded SYSTEM_RUN_DENIED or INVALID_REQUEST) as the authoritative denial signal. They also honor node-host UNAVAILABLE wrappers around a nested structured error carrying one of those codes.

Cron does not classify final-output prose or approval-looking refusal phrases as denials unless the embedded run also provides structured denial metadata, so ordinary assistant text is not treated as a blocked command.

cron list and run history surface the denial reason instead of reporting a blocked command as ok.

Retention

Retention behavior:

  • cron.sessionRetention (default 24h, or false to disable) prunes completed isolated run sessions.
  • Run history keeps the newest 2000 terminal rows per cron job. Lost rows retain the standard 24-hour lost-task cleanup window.

Migrating older jobs

Note

For cron jobs created before the current delivery and storage format, run openclaw doctor --fix. The Doctor command normalizes legacy cron fields (jobId, schedule.cron, top-level delivery fields including legacy threadId, payload provider delivery aliases) and converts notify: true webhook fallback jobs from the deprecated raw cron.webhook value to explicit webhook delivery, then removes that configuration key. Jobs already announcing to a chat retain that delivery and receive a completion webhook destination. When no legacy webhook exists, the inactive top-level notify marker is removed for jobs lacking a migration target (the existing delivery stays unchanged), so doctor --fix stops repeatedly warning about them.

Common edits

Adjust delivery settings while keeping the message intact:

openclaw cron edit <job-id> --announce --channel telegram --to "123456789"

Turn off delivery for a single job:

openclaw cron edit <job-id> --no-deliver

Activate lightweight bootstrap context for a single job:

openclaw cron edit <job-id> --light-context

Send announcements to a designated channel:

openclaw cron edit <job-id> --announce --channel slack --to "channel:C1234567890"

Send announcements to a Telegram forum topic:

openclaw cron edit <job-id> --announce --channel telegram --to "-1001234567890" --thread-id 42

Set up a single job with lightweight bootstrap context:

openclaw cron create "0 7 * * *" \
  "Summarize overnight updates." \
  --name "Lightweight morning brief" \
  --session isolated \
  --light-context \
  --no-deliver

--light-context only applies to isolated agent-turn jobs. For cron runs, lightweight mode leaves the bootstrap context empty rather than injecting the full workspace bootstrap set.

Build a command job with precise argv, cwd, env, stdin, and output limits:

openclaw cron create "*/30 * * * *" \
  --name "Position export" \
  --command-argv '["node","scripts/export-position.mjs"]' \
  --command-cwd "/srv/app" \
  --command-env "NODE_ENV=production" \
  --command-input '{"mode":"summary"}' \
  --timeout-seconds 120 \
  --no-output-timeout-seconds 30 \
  --output-max-bytes 65536 \
  --webhook "https://example.invalid/openclaw/cron"

Common admin commands

Manual execution and review:

openclaw cron list
openclaw cron list --agent ops
openclaw cron get <job-id>
openclaw cron show <job-id>
openclaw cron run <job-id>
openclaw cron run <job-id> --due
openclaw cron run <job-id> --wait --wait-timeout 10m
openclaw cron run <job-id> --wait --wait-timeout 10m --poll-interval 2s
openclaw cron runs --id <job-id> --limit 50
openclaw cron runs --id <job-id> --run-id <run-id>

By default, openclaw cron list lists enabled jobs. Use --all to also show disabled jobs, or --agent <id> to display only jobs whose effective normalized agent id matches; jobs without a stored agent id are treated as the configured default agent.

openclaw cron get <job-id> outputs the stored job JSON directly. Use cron show <job-id> when you need the human-readable view with a delivery-route preview.

cron list --json and cron show <job-id> --json include a top-level status field on every job, derived from enabled, state.runningAtMs, and state.lastRunStatus. Possible values: disabled, running, ok, error, skipped, or idle. The JSON status remains canonical and undecorated so external tools can read job state without re-deriving it; human output may annotate repeated error statuses with a failure count.

cron runs entries contain delivery diagnostics showing the intended cron target, the resolved target, message-tool sends, fallback usage, and delivered state.

Private per-job scratch storage (used for heartbeat checklists and similar monitor context):

openclaw cron scratch <job-id>                  # print current scratch content
openclaw cron scratch <job-id> --json           # scratch plus revision metadata
openclaw cron scratch <job-id> --set "text"     # replace scratch with exact text
openclaw cron scratch <job-id> --file notes.md  # replace scratch from a file (- for stdin)
openclaw cron scratch <job-id> --unset          # remove the scratch row

Scratch resides in the shared state database, capped at 256 KiB, and is never included in cron list/cron get/cron runs output. Writes use compare-and-swap, guarded against the revision read at command start; pass --expected-revision <n> to pin a specific revision instead. See Heartbeat for how heartbeat monitors use scratch.

Agent and session retargeting:

openclaw cron edit <job-id> --agent ops
openclaw cron edit <job-id> --clear-agent
openclaw cron edit <job-id> --session current
openclaw cron edit <job-id> --session "session:daily-brief"

openclaw cron add issues a warning when --agent is omitted on agent-turn jobs and falls back to the default agent (main). At creation time, pass --agent <id> to pin a specific agent.

Delivery adjustments:

openclaw cron edit <job-id> --announce --channel slack --to "channel:C1234567890"
openclaw cron edit <job-id> --webhook "https://example.invalid/openclaw/cron"
openclaw cron edit <job-id> --best-effort-deliver
openclaw cron edit <job-id> --no-best-effort-deliver
openclaw cron edit <job-id> --no-deliver