Quick start
How cron works
- Cron runs inside the Gateway process (not inside the model).
- Job definitions persist at
~/.openclaw/cron/jobs.jsonso restarts do not lose schedules. - Runtime execution state persists next to it in
~/.openclaw/cron/jobs-state.json. If you track cron definitions in git, trackjobs.jsonand gitignorejobs-state.json. - After the split, older OpenClaw versions can read
jobs.jsonbut may treat jobs as fresh because runtime fields now live injobs-state.json. - All cron executions create background task records.
- One-shot jobs (
--at) auto-delete after success by default. - Isolated cron runs best-effort close tracked browser tabs/processes for their
cron:<jobId>session when the run completes, so detached browser automation does not leave orphaned processes behind. - Isolated cron runs also guard against stale acknowledgement replies. If the
first result is just an interim status update (
on it,pulling everything together, and similar hints) and no descendant subagent run is still responsible for the final answer, OpenClaw re-prompts once for the actual result before delivery.
lost.
Schedule types
| Kind | CLI flag | Description |
|---|---|---|
at | --at | One-shot timestamp (ISO 8601 or relative like 20m) |
every | --every | Fixed interval |
cron | --cron | 5-field or 6-field cron expression with optional --tz |
--tz America/New_York for local wall-clock scheduling.
Recurring top-of-hour expressions are automatically staggered by up to 5 minutes to reduce load spikes. Use --exact to force precise timing or --stagger 30s for an explicit window.
Day-of-month and day-of-week use OR logic
Cron expressions are parsed by croner. When both the day-of-month and day-of-week fields are non-wildcard, croner matches when either field matches — not both. This is standard Vixie cron behavior.+ day-of-week modifier (0 9 15 * +1) or schedule on one field and guard the other in your job’s prompt or command.
Execution styles
| Style | --session value | Runs in | Best for |
|---|---|---|---|
| Main session | main | Next heartbeat turn | Reminders, system events |
| Isolated | isolated | Dedicated cron:<jobId> | Reports, background chores |
| Current session | current | Bound at creation time | Context-aware recurring work |
| Custom session | session:custom-id | Persistent named session | Workflows that build on history |
--wake now or --wake next-heartbeat). Those system events do not extend daily/idle reset freshness for the target session. Isolated jobs run a dedicated agent turn with a fresh session. Custom sessions (session:xxx) persist context across runs, enabling workflows like daily standups that build on previous summaries.
For isolated jobs, “fresh session” means a new transcript/session id for each run. OpenClaw may carry safe preferences such as thinking/fast/verbose settings, labels, and explicit user-selected model/auth overrides, but it does not inherit ambient conversation context from an older cron row: channel/group routing, send or queue policy, elevation, origin, or ACP runtime binding. Use current or session:<id> when a recurring job should deliberately build on the same conversation context.
For isolated jobs, runtime teardown now includes best-effort browser cleanup for that cron session. Cleanup failures are ignored so the actual cron result still wins.
Isolated cron runs also dispose any bundled MCP runtime instances created for the job through the shared runtime-cleanup path. This matches how main-session and custom-session MCP clients are torn down, so isolated cron jobs do not leak stdio child processes or long-lived MCP connections across runs.
When isolated cron runs orchestrate subagents, delivery also prefers the final
descendant output over stale parent interim text. If descendants are still
running, OpenClaw suppresses that partial parent update instead of announcing it.
For text-only Discord announce targets, OpenClaw sends the canonical final
assistant text once instead of replaying both streamed/intermediate text payloads
and the final answer. Media and structured Discord payloads are still delivered
as separate payloads so attachments and components are not dropped.
Payload options for isolated jobs
--message: prompt text (required for isolated)--model/--thinking: model and thinking level overrides--light-context: skip workspace bootstrap file injection--tools exec,read: restrict which tools the job can use
--model uses the selected allowed model for that job. If the requested model
is not allowed, cron logs a warning and falls back to the job’s agent/default
model selection instead. Configured fallback chains still apply, but a plain
model override with no explicit per-job fallback list no longer appends the
agent primary as a hidden extra retry target.
Model-selection precedence for isolated jobs is:
- Gmail hook model override (when the run came from Gmail and that override is allowed)
- Per-job payload
model - User-selected stored cron session model override
- Agent/default model selection
params.fastMode, isolated cron uses that by default. A stored session
fastMode override still wins over config in either direction.
If an isolated run hits a live model-switch handoff, cron retries with the
switched provider/model and persists that live selection for the active run
before retrying. When the switch also carries a new auth profile, cron persists
that auth profile override for the active run too. Retries are bounded: after
the initial attempt plus 2 switch retries, cron aborts instead of looping
forever.
Delivery and output
| Mode | What happens |
|---|---|
announce | Fallback-deliver final text to the target if the agent did not send |
webhook | POST finished event payload to a URL |
none | No runner fallback delivery |
--announce --channel telegram --to "-1001234567890" for channel delivery. For Telegram forum topics, use -1001234567890:topic:123. Slack/Discord/Mattermost targets should use explicit prefixes (channel:<id>, user:<id>). Matrix room IDs are case-sensitive; use the exact room ID or room:!room:server form from Matrix.
For isolated jobs, chat delivery is shared. If a chat route is available, the
agent can use the message tool even when the job uses --no-deliver. If the
agent sends to the configured/current target, OpenClaw skips the fallback
announce. Otherwise announce, webhook, and none only control what the
runner does with the final reply after the agent turn.
When an agent creates an isolated reminder from an active chat, OpenClaw stores
the preserved live delivery target for the fallback announce route. Internal
session keys may be lowercase; provider delivery targets are not reconstructed
from those keys when current chat context is available.
Failure notifications follow a separate destination path:
cron.failureDestinationsets a global default for failure notifications.job.delivery.failureDestinationoverrides that per job.- If neither is set and the job already delivers via
announce, failure notifications now fall back to that primary announce target. delivery.failureDestinationis only supported onsessionTarget="isolated"jobs unless the primary delivery mode iswebhook.
CLI examples
One-shot reminder (main session):Webhooks
Gateway can expose HTTP webhook endpoints for external triggers. Enable in config:Authentication
Every request must include the hook token via header:Authorization: Bearer <token>(recommended)x-openclaw-token: <token>
POST /hooks/wake
Enqueue a system event for the main session:text(required): event descriptionmode(optional):now(default) ornext-heartbeat
POST /hooks/agent
Run an isolated agent turn:message (required), name, agentId, wakeMode, deliver, channel, to, model, thinking, timeoutSeconds.
Mapped hooks (POST /hooks/<name>)
Custom hook names are resolved viahooks.mappings in config. Mappings can transform arbitrary payloads into wake or agent actions with templates or code transforms.
Security
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy.
- Use a dedicated hook token; do not reuse gateway auth tokens.
- Keep
hooks.pathon a dedicated subpath;/is rejected. - Set
hooks.allowedAgentIdsto limit explicitagentIdrouting. - Keep
hooks.allowRequestSessionKey=falseunless you require caller-selected sessions. - If you enable
hooks.allowRequestSessionKey, also sethooks.allowedSessionKeyPrefixesto constrain allowed session key shapes. - Hook payloads are wrapped with safety boundaries by default.
Gmail PubSub integration
Wire Gmail inbox triggers to OpenClaw via Google PubSub. Prerequisites:gcloud CLI, gog (gogcli), OpenClaw hooks enabled, Tailscale for the public HTTPS endpoint.
Wizard setup (recommended)
hooks.gmail config, enables the Gmail preset, and uses Tailscale Funnel for the push endpoint.
Gateway auto-start
Whenhooks.enabled=true and hooks.gmail.account is set, the Gateway starts gog gmail watch serve on boot and auto-renews the watch. Set OPENCLAW_SKIP_GMAIL_WATCHER=1 to opt out.
Manual one-time setup
- Select the GCP project that owns the OAuth client used by
gog:
- Create topic and grant Gmail push access:
- Start the watch:
Gmail model override
Managing jobs
openclaw cron add|edit --model ...changes the job’s selected model.- If the model is allowed, that exact provider/model reaches the isolated agent run.
- If it is not allowed, cron warns and falls back to the job’s agent/default model selection.
- Configured fallback chains still apply, but a plain
--modeloverride with no explicit per-job fallback list no longer falls through to the agent primary as a silent extra retry target.
Configuration
cron.store: a .json store such as
~/clawd/cron/jobs.json uses ~/clawd/cron/jobs-state.json, while a store path
without a .json suffix appends -state.json.
Disable cron: cron.enabled: false or OPENCLAW_SKIP_CRON=1.
One-shot retry: transient errors (rate limit, overload, network, server error) retry up to 3 times with exponential backoff. Permanent errors disable immediately.
Recurring retry: exponential backoff (30s to 60m) between retries. Backoff resets after the next successful run.
Maintenance: cron.sessionRetention (default 24h) prunes isolated run-session entries. cron.runLog.maxBytes / cron.runLog.keepLines auto-prune run-log files.
Troubleshooting
Command ladder
Cron not firing
- Check
cron.enabledandOPENCLAW_SKIP_CRONenv var. - Confirm the Gateway is running continuously.
- For
cronschedules, verify timezone (--tz) vs the host timezone. reason: not-duein run output means manual run was checked withopenclaw cron run <jobId> --dueand the job was not due yet.
Cron fired but no delivery
- Delivery mode
nonemeans no runner fallback send is expected. The agent can still send directly with themessagetool when a chat route is available. - Delivery target missing/invalid (
channel/to) means outbound was skipped. - For Matrix, copied or legacy jobs with lowercased
delivery.toroom IDs can fail because Matrix room IDs are case-sensitive. Edit the job to the exact!room:serverorroom:!room:servervalue from Matrix. - Channel auth errors (
unauthorized,Forbidden) mean delivery was blocked by credentials. - If the isolated run returns only the silent token (
NO_REPLY/no_reply), OpenClaw suppresses direct outbound delivery and also suppresses the fallback queued summary path, so nothing is posted back to chat. - If the agent should message the user itself, check that the job has a usable
route (
channel: "last"with a previous chat, or an explicit channel/target).
Cron or heartbeat appears to prevent /new-style rollover
- Daily and idle reset freshness is not based on
updatedAt; see Session management. - Cron wakeups, heartbeat runs, exec notifications, and gateway bookkeeping may
update the session row for routing/status, but they do not extend
sessionStartedAtorlastInteractionAt. - For legacy rows created before those fields existed, OpenClaw can recover
sessionStartedAtfrom the transcript JSONL session header when the file is still available. Legacy idle rows withoutlastInteractionAtuse that recovered start time as their idle baseline.
Timezone gotchas
- Cron without
--tzuses the gateway host timezone. atschedules without timezone are treated as UTC.- Heartbeat
activeHoursuses configured timezone resolution.
Related
- Automation & Tasks — all automation mechanisms at a glance
- Background Tasks — task ledger for cron executions
- Heartbeat — periodic main-session turns
- Timezone — timezone configuration