OpenClaw Automations CLI: Schedule and Run Background Jobs

Reference for the openclaw automations command, which schedules and runs background jobs via the Gateway scheduler. Covers subcommands, permissions, and connection options for CLI users.

Read this when

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

openclaw automations

Automation jobs for the Gateway scheduler are managed through these commands. openclaw automations serves as the primary entry point, while openclaw cron works as an alias; every subcommand listed here functions with either spelling.

Tip

To see the complete command surface, run openclaw automations --help. The conceptual guide is available at Automations.

Note

Every automation mutation, including add/create, update/edit, remove, and run, demands operator.admin. Command-payload executions run directly inside the Gateway process rather than as an agent tools.exec tool call; model-visible exec tools remain subject to tools.exec.* and exec approvals.

Shared Gateway connection options are accepted by every automation subcommand. For a Gateway running on a non-default local port, use --port <port>; for a specific WebSocket URL, use --url <url>. These two options are mutually exclusive. Connection options like --port, --url, and --token can be placed either before or after the subcommand.

Create jobs quickly

openclaw automations create is another name for openclaw automations add. When creating a new job, place the schedule first and the prompt second:

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

If the job should POST the completed payload instead of delivering to a chat target, use --webhook <url>:

openclaw automations 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 run within the OpenClaw scheduler without launching an isolated agent/model run, use --command:

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

--command <shell> persists argv: ["sh", "-lc", <shell>]. Exact argv execution is handled by --command-argv '["node","scripts/report.mjs"]'. Command jobs capture stdout/stderr, log standard run history, and send output through the same announce, webhook, or none delivery modes as isolated jobs. A command that outputs only NO_REPLY gets suppressed.

When the list and detail views should display a human-readable label distinct from the automation's stable name, use --display-name <name>. That label is set or updated via automations add|edit --display-name. To remove the label and bring back the stable name in list and detail views, use automations edit <job-id> --clear-display-name. The set and clear options are mutually exclusive.

Sessions

--session accepts one of main, isolated, current, or session:<id>.

Agent-turn jobs default to the conversation that created them when session context exists. If no session key is present, which covers ordinary CLI calls and API calls that omit one, the target falls back to isolated.

Session keys

  • The agent's main session is bound to by main.
  • A fresh transcript and session id are generated for each run by isolated.
  • At creation time, current binds to the active session.
  • An explicit persistent session key is pinned to by session:<id>.

Isolated session semantics

Ambient conversation context is reset for isolated runs. Channel and group routing, send/queue policy, elevation, origin, and ACP runtime binding all reset for the new run. Safe preferences and explicit user-selected model or auth overrides may persist across runs.

Delivery

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

Provider-prefixed targets help clear up ambiguity when an announce channel has not been resolved. As an example, to: "telegram:123" picks Telegram in cases where delivery.channel is left out or set to last. Only prefixes that the loaded plugin advertises count as provider selectors. When delivery.channel is given explicitly, the prefix has to match that channel; channel: "whatsapp" paired with to: "telegram:123" gets turned down. Service prefixes like imessage: and sms: stay as channel-owned target syntax.

Note

Jobs that are isolated and use automations add default to --announce for delivery. To keep output internal, apply --no-deliver. --deliver is still around as a deprecated alias for --announce.

Delivery ownership

Delivery for isolated automation chats is shared between the agent and the runner:

  • The agent can send directly via the message tool when a chat route exists.
  • announce handles fallback delivery of the final reply only if the agent did not send directly to the resolved target.
  • webhook sends the finished payload to a URL.
  • none turns off runner fallback delivery.

Webhook delivery gets configured with automations add|create --webhook <url> or automations edit <job-id> --webhook <url>. Avoid pairing --webhook with chat delivery flags like --announce, --no-deliver, --channel, --to, --thread-id, or --account.

Individual delivery routing fields can be cleared with automations edit <job-id> using --clear-channel, --clear-to, --clear-thread-id, and --clear-account (each gets rejected when used alongside its matching set flag). In contrast to --no-deliver, which only turns off runner fallback delivery, these delete the stored field so the job re-resolves that part of its route from defaults.

--announce serves as runner fallback delivery for the final reply. --no-deliver disables that fallback without removing the agent's message tool when a chat route is present.

Reminders spawned from an active chat keep the live chat delivery target for fallback announce delivery. Internal session keys might be lowercase; avoid relying on them as the authoritative source for case-sensitive provider IDs such as Matrix room IDs.

Failure delivery

Failure notifications get resolved in this sequence:

  1. Route fields inside the job's failureAlert object.
  2. delivery.failureDestination on the job, layered over the global destination fields on cron.failureAlert (mode, channel, to, accountId). The retired cron.failureDestination block gets merged into them by openclaw doctor --fix.
  3. The job's primary announce target (when neither of the above yields a concrete destination).

Jobs that have one of those routes default to an execution-failure alert after 2 consecutive failures with a 1-hour cooldown. A per-job or global failureAlert object explicitly activates or tunes the policy even without an existing route. failureAlert: false disables execution and required-delivery failure alerts for the job, but the auto-disable safety notification stays active. Global enabled: false disables inheritance unless the job carries its own failureAlert object. delivery.bestEffort: true suppresses inherited or default execution alerts, but not an explicit per-job policy.

Note

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

Chat failure notifications include the run start time in the agent's configured user timezone. Webhook message text remains stable and exposes the instant as runAtMs.

Isolated automation runs treat run-level agent failures as job errors even when no reply payload is produced, so model or provider failures still bump error counters and trigger failure notifications.

Command jobs do not start an isolated agent turn. A zero exit code records ok; non-zero exit, signal, timeout, or no-output timeout records error and can trigger the same failure notification path.

Required completion delivery is separate: status: "ok" with completionStatus: "failed" does not increment the execution streak or backoff. Delivery-failure alerts use a resolved alternate failure destination without the after threshold. Every alert, including the first delivery failure after an execution alert, honors the shared job or global failureAlert.cooldownMs (default 1 hour), never retrying the primary route that just failed.

When an isolated run hits its timeout before the first model request, openclaw automations show and openclaw automations runs carry a phase-specific error message such as setup timed out before runner start, or a stall notice that identifies the most recent startup phase (for instance context-engine). For providers backed by the CLI, the pre-model watchdog remains active until the external CLI turn begins, meaning stalls in session lookup, hook, auth, prompt, and CLI setup are flagged as pre-model automation failures.

Scheduling

One-shot jobs

A one-shot run is scheduled via --at <datetime>. Datetimes without an offset are assumed to be UTC unless you supply --tz <iana>, which treats the wall-clock time as belonging to the specified timezone.

Note

One-shot jobs are deleted only after completionStatus: "succeeded". If a required delivery fails or completion is unknown, the job stays disabled with no upcoming run, so restarts won't replay payload side effects. Deliberate silence and successful runs with explicit delivery.bestEffort: true complete and delete as normal. To also keep successful jobs, use --keep-after-run.

Recurring jobs

Recurring jobs apply exponential retry backoff after consecutive errors: 30s, 1m, 5m, 15m, 60m. Once a run succeeds, the schedule resumes its normal cadence.

Skipped runs are counted separately from execution errors. They don't influence retry backoff, but openclaw automations edit <job-id> --failure-alert-include-skipped can route failure alerts into repeated notifications for skipped runs.

For isolated jobs aimed at a local configured model provider (base URL on loopback, a private network, or .local), the scheduler performs a lightweight provider preflight before the agent turn starts: api: "ollama" providers get probed at /api/tags; other local OpenAI-compatible providers (api: "openai-completions", such as vLLM, SGLang, LM Studio) are probed at /models. If the endpoint can't be reached, the run is marked as skipped and retried on a later schedule; the reachability check is cached per endpoint for 5 minutes, so many jobs against the same local server avoid hammering it with repeated probes.

Automation jobs, pending runtime state, and run history all reside 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, schedules are edited with openclaw automations add|edit|remove rather than by editing JSON files.

Manual runs

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

openclaw automations run <job-id>
openclaw automations 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 automations run <job-id> --wait --wait-timeout 10m --poll-interval 2s

With --wait, the CLI calls cron.run first, then polls the durable cron.runs row for the returned runId; it doesn't re-read mutable job delivery settings. JSON reports payload execution as status and whole-run completion as completionStatus. The command exits 0 only for completionStatus: "succeeded"; failed, unknown, execution errors/skips, a missing runId, and timeout expiry exit non-zero (default 10m, polled every 2s by default). --poll-interval must be greater than zero. Completed JSON output, including the run summary, is flushed before the command exits, so it can be piped to a JSON reader.

Note

Use --due when you want the manual command to run only if the job is currently due. If --due --wait doesn't enqueue a run, the command returns the normal non-run response instead of polling.

Models

automations add|edit --model <ref> selects an allowed model for the job. automations add|edit --fallbacks <list> sets per-job fallback models, for example --fallbacks openrouter/gpt-4.1-mini,openai/gpt-5; pass --fallbacks "" for a strict run with no fallbacks. automations edit <job-id> --clear-fallbacks removes the per-job fallback override. automations edit <job-id> --clear-model removes the per-job model override so the job follows normal automation model-selection precedence (a stored automation-session override if present, otherwise the agent/default model); it cannot be combined with --model. automations add|edit --thinking <level> sets a per-job thinking override; automations edit <job-id> --clear-thinking removes it so the job follows normal automation thinking precedence, and it cannot be combined with --thinking.

Warning

If the model is not allowed or cannot be resolved, the scheduler fails the run with an explicit validation error instead of falling back to the job's agent or default model selection.

The automation --model is a job primary, not a chat-session /model override. That means:

  • Model fallbacks that you have configured remain in effect if the job's chosen model fails.
  • When present, the per-job payload fallbacks supersedes the configured fallback list.
  • A per-job fallback list that is empty (--fallbacks "" or fallbacks: [] in the job payload/API) enforces strict run behavior.
  • If a job includes --model but no fallback list is set, OpenClaw passes an explicit empty fallback override, preventing the agent primary from being silently added as a retry target.
  • Before marking a run skipped, local-provider preflight checks evaluate the configured fallbacks.

openclaw doctor identifies jobs that already have payload.model configured, including provider namespace counts and discrepancies against agents.defaults.model. Run this check when auth, provider, or billing behavior differs between live chat and scheduled jobs.

Isolated automation model precedence

For isolated automation runs, the active model resolves in this sequence:

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

Fast mode

Isolated automation fast mode follows the resolved live model selection. It resolves stored session fastMode, per-agent agents.entries.*.fastModeDefault, global agents.defaults.fastModeDefault, then selected-model params.fastMode. 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, the scheduler persists the switched provider and model (plus the switched auth profile override, if present) for the active run before retrying. The outer retry loop allows two switch retries after the initial attempt, then aborts to avoid an infinite loop.

Run output and denials

Stale acknowledgement suppression

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

Silent token suppression

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

Human-readable automations list and automations show label successful intentional suppression as ok (suppressed), not a delivery warning. automations show includes last delivery suppression with the recorded reason (empty, silent, heartbeat, or channel_transform). JSON keeps deliveryStatus: "not-delivered" and the separate deliverySuppressionReason; genuine delivery failures without an intentional reason still show ok (not delivered) when execution succeeded.

Structured denials

Isolated automation 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.

The scheduler 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.

automations 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; a zero duration such as "0h" also disables) prunes completed isolated run sessions.
  • Terminal run history is retained for 7 days (lost rows for 24 hours), with the newest 2000 rows per job and history class enforced as an additional ceiling.

Migrating older jobs

Note

If you have automation jobs from before the current delivery and store format, run openclaw doctor --fix. Doctor normalizes legacy job fields (jobId, schedule.cron, top-level delivery fields including legacy threadId, payload provider delivery aliases) and migrates notify: true webhook fallback jobs from the retired raw cron.webhook value to explicit webhook delivery before removing that config key. Jobs that already announce to a chat keep that delivery and get a completion webhook destination. Without a legacy webhook, the inert top-level notify marker is removed for jobs with no migration target (the existing delivery is preserved unchanged), so doctor --fix no longer keeps re-warning about them.

Common edits

Update delivery settings without changing the message:

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

Disable delivery for a job that runs in isolation:

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

Turn on a minimal bootstrap context for an isolated job:

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

Send a message to a chosen channel:

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

Post to a topic within a Telegram forum:

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

Set up an isolated job with a lightweight bootstrap context:

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

--light-context is limited to isolated agent-turn jobs. When automation runs, lightweight mode leaves bootstrap context empty instead of loading the complete workspace bootstrap set.

Build a command job with explicit argv, cwd, env, stdin, and output caps:

openclaw automations 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 automations list
openclaw automations list --agent ops
openclaw automations get <job-id>
openclaw automations get <job-id> --json
openclaw automations show <job-id>
openclaw automations run <job-id>
openclaw automations run <job-id> --due
openclaw automations run <job-id> --wait --wait-timeout 10m
openclaw automations run <job-id> --wait --wait-timeout 10m --poll-interval 2s
openclaw automations runs --id <job-id> --limit 50
openclaw automations runs --id <job-id> --limit 50 --json
openclaw automations runs --id <job-id> --run-id <run-id>

By default, openclaw automations list lists enabled jobs. Add --all to also show disabled ones, or --agent <id> to filter for jobs whose effective normalized agent id matches; jobs lacking a stored agent id are treated as the configured default agent.

--json always asks for JSON output. Commands that already produce a machine-readable result return JSON by default: add/create, status, enable, disable, rm/remove/delete, run, edit, get, and runs. For those, --json serves as the explicit machine-output flag. openclaw automations get <job-id> hands back the stored job JSON as-is; pick automations show <job-id> for the human-readable version that previews delivery routes.

list and show default to human-readable formatting and flip to JSON with --json. scratch pulls raw scratch content by default, while --json prints the scratch plus revision metadata; scratch writes return the revision result as JSON by default and take --json as the explicit machine-output spelling.

automations list --json and automations show <job-id> --json attach a top-level status field to each job, derived from enabled, state.runningAtMs, and state.lastRunStatus. Possible values: disabled, running, ok, error, skipped, or idle. JSON status remains canonical and unadorned so external tools can read job state without recomputing it; human output may annotate repeated error statuses with a failure count.

automations runs entries carry delivery diagnostics covering the intended automation target, the resolved target, message-tool sends, fallback usage, and delivered state.

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

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

Scratch lives in the shared state database, limited to 256 KiB, and is excluded from automations list/automations get/automations runs output. Writes use compare-and-swap against the revision read at command start; pass --expected-revision <n> to lock in a specific revision. See Heartbeat for how heartbeat monitors make use of scratch.

Agent and session retargeting:

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

openclaw automations add flags a missing --agent on agent-turn jobs and falls back to the default agent (main). To fix a specific agent, pass --agent <id> at create time.

Delivery tweaks:

openclaw automations edit <job-id> --announce --channel slack --to "channel:C1234567890"
openclaw automations edit <job-id> --webhook "https://example.invalid/openclaw/cron"
openclaw automations edit <job-id> --best-effort-deliver
openclaw automations edit <job-id> --no-best-effort-deliver
openclaw automations edit <job-id> --no-deliver
3,322 words · updated Sep 1, 2026