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 --helpto 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) needsoperator.admin. Commands run in the Gateway process directly, not through an agenttools.exectool 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
mainattaches to the agent's primary session.isolatedgenerates a new transcript and session id for every run.currentattaches 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 addjobs use--announcedelivery by default. To keep output internal, use--no-deliver.--deliveris 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
messagetool to send directly. - The
announceflag delivers the final reply as a fallback only if the agent did not send directly to the resolved target. webhooksends the completed payload to a specified URL.noneturns 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:
delivery.failureDestinationon the job.- Global
cron.failureDestination. - The job's primary announce target (when neither of the above resolves to a concrete destination).
Note
Main-session jobs can only use
delivery.failureDestinationwhen the primary delivery mode iswebhook. 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-runto 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
--duewhen you want the manual command to execute only if the job is currently due. If--due --waitdoes 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
fallbacksreplaces the configured fallback list when present. - An empty per-job fallback list (
--fallbacks ""orfallbacks: []in the job payload or API) makes the cron run strict. - When a job has
--modelbut 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:
- Gmail-hook override.
- Per-job
--model. - Stored cron-session model override (when the user selected one).
- 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(default24h, orfalseto 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 legacythreadId, payloadproviderdelivery aliases) and convertsnotify: truewebhook fallback jobs from the deprecated rawcron.webhookvalue 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-levelnotifymarker is removed for jobs lacking a migration target (the existing delivery stays unchanged), sodoctor --fixstops 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