coderClaw

Cron jobs (Gateway scheduler)

Cron vs Heartbeat? See Cron vs Heartbeat for guidance on when to use each.

Cron is the Gateway’s built-in scheduler. It persists jobs, wakes the agent at the right time, and can optionally deliver output back to a chat.

If you want “run this every morning” or “poke the agent in 20 minutes”, cron is the mechanism.

Troubleshooting: /automation/troubleshooting

TL;DR

Quick start (actionable)

Create a one-shot reminder, verify it exists, and run it immediately:

coderclaw 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

coderclaw cron list
coderclaw cron run <job-id>
coderclaw cron runs --id <job-id>

Schedule a recurring isolated job with delivery:

coderclaw cron add \
  --name "Morning brief" \
  --cron "0 7 * * *" \
  --tz "America/Los_Angeles" \
  --session isolated \
  --message "Summarize overnight updates." \
  --announce \
  --channel slack \
  --to "channel:C1234567890"

Tool-call equivalents (Gateway cron tool)

For the canonical JSON shapes and examples, see JSON schema for tool calls.

Where cron jobs are stored

Cron jobs are persisted on the Gateway host at ~/.coderclaw/cron/jobs.json by default. The Gateway loads the file into memory and writes it back on changes, so manual edits are only safe when the Gateway is stopped. Prefer coderclaw cron add/edit or the cron tool call API for changes.

Beginner-friendly overview

Think of a cron job as: when to run + what to do.

  1. Choose a schedule
    • One-shot reminder → schedule.kind = "at" (CLI: --at)
    • Repeating job → schedule.kind = "every" or schedule.kind = "cron"
    • If your ISO timestamp omits a timezone, it is treated as UTC.
  2. Choose where it runs
    • sessionTarget: "main" → run during the next heartbeat with main context.
    • sessionTarget: "isolated" → run a dedicated agent turn in cron:<jobId>.
  3. Choose the payload
    • Main session → payload.kind = "systemEvent"
    • Isolated session → payload.kind = "agentTurn"

Optional: one-shot jobs (schedule.kind = "at") delete after success by default. Set deleteAfterRun: false to keep them (they will disable after success).

Concepts

Jobs

A cron job is a stored record with:

Jobs are identified by a stable jobId (used by CLI/Gateway APIs). In agent tool calls, jobId is canonical; legacy id is accepted for compatibility. One-shot jobs auto-delete after success by default; set deleteAfterRun: false to keep them.

Schedules

Cron supports three schedule kinds:

Cron expressions use croner. If a timezone is omitted, the Gateway host’s local timezone is used.

To reduce top-of-hour load spikes across many gateways, CoderClaw applies a deterministic per-job stagger window of up to 5 minutes for recurring top-of-hour expressions (for example 0 * * * *, 0 */2 * * *). Fixed-hour expressions such as 0 7 * * * remain exact.

For any cron schedule, you can set an explicit stagger window with schedule.staggerMs (0 keeps exact timing). CLI shortcuts:

Main vs isolated execution

Main session jobs (system events)

Main jobs enqueue a system event and optionally wake the heartbeat runner. They must use payload.kind = "systemEvent".

This is the best fit when you want the normal heartbeat prompt + main-session context. See Heartbeat.

Isolated jobs (dedicated cron sessions)

Isolated jobs run a dedicated agent turn in session cron:<jobId>.

Key behaviors:

Use isolated jobs for noisy, frequent, or “background chores” that shouldn’t spam your main chat history.

Payload shapes (what runs)

Two payload kinds are supported:

Common agentTurn fields:

Delivery config:

Announce delivery suppresses messaging tool sends for the run; use delivery.channel/delivery.to to target the chat instead. When delivery.mode = "none", no summary is posted to the main session.

If delivery is omitted for isolated jobs, CoderClaw defaults to announce.

Announce delivery flow

When delivery.mode = "announce", cron delivers directly via the outbound channel adapters. The main agent is not spun up to craft or forward the message.

Behavior details:

Webhook delivery flow

When delivery.mode = "webhook", cron posts the finished event payload to delivery.to when the finished event includes a summary.

Behavior details:

Model and thinking overrides

Isolated jobs (agentTurn) can override the model and thinking level:

Note: You can set model on main-session jobs too, but it changes the shared main session model. We recommend model overrides only for isolated jobs to avoid unexpected context shifts.

Resolution priority:

  1. Job payload override (highest)
  2. Hook-specific defaults (e.g., hooks.gmail.model)
  3. Agent config default

Delivery (channel + target)

Isolated jobs can deliver output to a channel via the top-level delivery config:

announce delivery is only valid for isolated jobs (sessionTarget: "isolated"). webhook delivery is valid for both main and isolated jobs.

If delivery.channel or delivery.to is omitted, cron can fall back to the main session’s “last route” (the last place the agent replied).

Target format reminders:

Telegram delivery targets (topics / forum threads)

Telegram supports forum topics via message_thread_id. For cron delivery, you can encode the topic/thread into the to field:

Prefixed targets like telegram:... / telegram:group:... are also accepted:

JSON schema for tool calls

Use these shapes when calling Gateway cron.* tools directly (agent tool calls or RPC). CLI flags accept human durations like 20m, but tool calls should use an ISO 8601 string for schedule.at and milliseconds for schedule.everyMs.

cron.add params

One-shot, main session job (system event):

{
  "name": "Reminder",
  "schedule": { "kind": "at", "at": "2026-02-01T16:00:00Z" },
  "sessionTarget": "main",
  "wakeMode": "now",
  "payload": { "kind": "systemEvent", "text": "Reminder text" },
  "deleteAfterRun": true
}

Recurring, isolated job with delivery:

{
  "name": "Morning brief",
  "schedule": { "kind": "cron", "expr": "0 7 * * *", "tz": "America/Los_Angeles" },
  "sessionTarget": "isolated",
  "wakeMode": "next-heartbeat",
  "payload": {
    "kind": "agentTurn",
    "message": "Summarize overnight updates."
  },
  "delivery": {
    "mode": "announce",
    "channel": "slack",
    "to": "channel:C1234567890",
    "bestEffort": true
  }
}

Notes:

cron.update params

{
  "jobId": "job-123",
  "patch": {
    "enabled": false,
    "schedule": { "kind": "every", "everyMs": 3600000 }
  }
}

Notes:

cron.run and cron.remove params

{ "jobId": "job-123", "mode": "force" }
{ "jobId": "job-123" }

Storage & history

Configuration

{
  cron: {
    enabled: true, // default true
    store: "~/.coderclaw/cron/jobs.json",
    maxConcurrentRuns: 1, // default 1
    webhook: "https://example.invalid/legacy", // deprecated fallback for stored notify:true jobs
    webhookToken: "replace-with-dedicated-webhook-token", // optional bearer token for webhook mode
  },
}

Webhook behavior:

Disable cron entirely:

CLI quickstart

One-shot reminder (UTC ISO, auto-delete after success):

coderclaw cron add \
  --name "Send reminder" \
  --at "2026-01-12T18:00:00Z" \
  --session main \
  --system-event "Reminder: submit expense report." \
  --wake now \
  --delete-after-run

One-shot reminder (main session, wake immediately):

coderclaw cron add \
  --name "Calendar check" \
  --at "20m" \
  --session main \
  --system-event "Next heartbeat: check calendar." \
  --wake now

Recurring isolated job (announce to WhatsApp):

coderclaw cron add \
  --name "Morning status" \
  --cron "0 7 * * *" \
  --tz "America/Los_Angeles" \
  --session isolated \
  --message "Summarize inbox + calendar for today." \
  --announce \
  --channel whatsapp \
  --to "+15551234567"

Recurring cron job with explicit 30-second stagger:

coderclaw cron add \
  --name "Minute watcher" \
  --cron "0 * * * * *" \
  --tz "UTC" \
  --stagger 30s \
  --session isolated \
  --message "Run minute watcher checks." \
  --announce

Recurring isolated job (deliver to a Telegram topic):

coderclaw cron add \
  --name "Nightly summary (topic)" \
  --cron "0 22 * * *" \
  --tz "America/Los_Angeles" \
  --session isolated \
  --message "Summarize today; send to the nightly topic." \
  --announce \
  --channel telegram \
  --to "-1001234567890:topic:123"

Isolated job with model and thinking override:

coderclaw 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 \
  --channel whatsapp \
  --to "+15551234567"

Agent selection (multi-agent setups):

# Pin a job to agent "ops" (falls back to default if that agent is missing)
coderclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops

# Switch or clear the agent on an existing job
coderclaw cron edit <jobId> --agent ops
coderclaw cron edit <jobId> --clear-agent

Manual run (force is the default, use --due to only run when due):

coderclaw cron run <jobId>
coderclaw cron run <jobId> --due

Edit an existing job (patch fields):

coderclaw cron edit <jobId> \
  --message "Updated prompt" \
  --model "opus" \
  --thinking low

Force an existing cron job to run exactly on schedule (no stagger):

coderclaw cron edit <jobId> --exact

Run history:

coderclaw cron runs --id <jobId> --limit 50

Immediate system event without creating a job:

coderclaw system event --mode now --text "Next heartbeat: check battery."

Gateway API surface

Troubleshooting

“Nothing runs”

A recurring job keeps delaying after failures

Telegram delivers to the wrong place

Subagent announce delivery retries