10 KiB
summary, read_when, title
| summary | read_when | title | |||
|---|---|---|---|---|---|
| Scheduled jobs, webhooks, and Gmail PubSub triggers for the Gateway scheduler |
|
Scheduled Tasks |
Scheduled Tasks (Cron)
Cron is the Gateway's built-in scheduler. It persists jobs, wakes the agent at the right time, and can deliver output back to a chat channel or webhook endpoint.
Quick start
# Add a one-shot reminder
openclaw cron add \
--name "Reminder" \
--at "2026-02-01T16:00:00Z" \
--session main \
--system-event "Reminder: check the cron docs draft" \
--wake now \
--delete-after-run
# Check your jobs
openclaw cron list
# See run history
openclaw cron runs --id <job-id>
How cron works
- Cron runs inside the Gateway process (not inside the model).
- Jobs persist at
~/.openclaw/cron/jobs.jsonso restarts do not lose schedules. - All cron executions create background task records.
- One-shot jobs (
--at) auto-delete after success by default.
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 |
Timestamps without a timezone are treated as UTC. Add --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.
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 |
Main session jobs enqueue a system event and optionally wake the heartbeat (--wake now or --wake next-heartbeat). 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.
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
Delivery and output
| Mode | What happens |
|---|---|
announce |
Deliver summary to target channel (default for isolated) |
webhook |
POST finished event payload to a URL |
none |
Internal only, no delivery |
Use --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>).
CLI examples
One-shot reminder (main session):
openclaw cron add \
--name "Calendar check" \
--at "20m" \
--session main \
--system-event "Next heartbeat: check calendar." \
--wake now
Recurring isolated job with delivery:
openclaw cron add \
--name "Morning brief" \
--cron "0 7 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Summarize overnight updates." \
--announce \
--channel slack \
--to "channel:C1234567890"
Isolated job with model and thinking override:
openclaw cron add \
--name "Deep analysis" \
--cron "0 6 * * 1" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Weekly deep analysis of project progress." \
--model "opus" \
--thinking high \
--announce
Webhooks
Gateway can expose HTTP webhook endpoints for external triggers. Enable in config:
{
hooks: {
enabled: true,
token: "shared-secret",
path: "/hooks",
},
}
Authentication
Every request must include the hook token via header:
Authorization: Bearer <token>(recommended)x-openclaw-token: <token>
Query-string tokens are rejected.
POST /hooks/wake
Enqueue a system event for the main session:
curl -X POST http://127.0.0.1:18789/hooks/wake \
-H 'Authorization: Bearer SECRET' \
-H 'Content-Type: application/json' \
-d '{"text":"New email received","mode":"now"}'
text(required): event descriptionmode(optional):now(default) ornext-heartbeat
POST /hooks/agent
Run an isolated agent turn:
curl -X POST http://127.0.0.1:18789/hooks/agent \
-H 'Authorization: Bearer SECRET' \
-H 'Content-Type: application/json' \
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.2-mini"}'
Fields: message (required), name, agentId, wakeMode, deliver, channel, to, model, thinking, timeoutSeconds.
Mapped hooks (POST /hooks/<name>)
Custom hook names are resolved via hooks.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.
- Set
hooks.allowedAgentIdsto limit explicitagentIdrouting. - Keep
hooks.allowRequestSessionKey=falseunless you require caller-selected sessions. - 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)
openclaw webhooks gmail setup --account openclaw@gmail.com
This writes hooks.gmail config, enables the Gmail preset, and uses Tailscale Funnel for the push endpoint.
Gateway auto-start
When hooks.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:
gcloud auth login
gcloud config set project <project-id>
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
- Create topic and grant Gmail push access:
gcloud pubsub topics create gog-gmail-watch
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
--member=serviceAccount:gmail-api-push@system.gserviceaccount.com \
--role=roles/pubsub.publisher
- Start the watch:
gog gmail watch start \
--account openclaw@gmail.com \
--label INBOX \
--topic projects/<project-id>/topics/gog-gmail-watch
Gmail model override
{
hooks: {
gmail: {
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
thinking: "off",
},
},
}
Managing jobs
# List all jobs
openclaw cron list
# Edit a job
openclaw cron edit <jobId> --message "Updated prompt" --model "opus"
# Force run a job now
openclaw cron run <jobId>
# Run only if due
openclaw cron run <jobId> --due
# View run history
openclaw cron runs --id <jobId> --limit 50
# Delete a job
openclaw cron remove <jobId>
# Agent selection (multi-agent setups)
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
openclaw cron edit <jobId> --clear-agent
Configuration
{
cron: {
enabled: true,
store: "~/.openclaw/cron/jobs.json",
maxConcurrentRuns: 1,
retry: {
maxAttempts: 3,
backoffMs: [60000, 120000, 300000],
retryOn: ["rate_limit", "overloaded", "network", "server_error"],
},
webhookToken: "replace-with-dedicated-webhook-token",
sessionRetention: "24h",
runLog: { maxBytes: "2mb", keepLines: 2000 },
},
}
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
openclaw status
openclaw gateway status
openclaw cron status
openclaw cron list
openclaw cron runs --id <jobId> --limit 20
openclaw system heartbeat last
openclaw logs --follow
openclaw doctor
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 called without--force.
Cron fired but no delivery
- Delivery mode is
nonemeans no external message is expected. - Delivery target missing/invalid (
channel/to) means outbound was skipped. - Channel auth errors (
unauthorized,Forbidden) mean delivery was blocked by credentials.
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