You trigger the job manually and it works perfectly. Output is clean, delivery arrives, everything looks right. But when the scheduled time arrives, nothing happens. No message, no error, no indication the job fired at all. This is one of the most disorienting failure modes in OpenClaw, because the job appears healthy in every way except the one that matters: it does not run when you are not watching. This article covers the five root causes of openclaw cron production failure, how to confirm which one you have, and the exact fix for each.
TL;DR
- Root cause 1: Timezone mismatch , the job is firing at the wrong clock time.
- Root cause 2: Gateway restart cleared the schedule , disable/re-enable to reschedule.
- Root cause 3: Delivery misconfigured , the job runs but the result goes nowhere you see.
- Root cause 4: Model unavailable , the isolated session fails silently when the model is down.
- Root cause 5: systemEvent target , main session was idle when the event was injected.
Throughout this article you will see indented blocks like the ones below. Each one is a command you can paste directly into your OpenClaw chat. Your agent will run it and report back. You do not need to open a terminal or edit any files manually.
Start here: confirm the job is actually firing
Before diagnosing why production is silent, confirm whether the job is firing at all and being ignored, or genuinely not firing.
Show me cron job [job-id] details: enabled status, last run time, last run result, and the next scheduled run time in both UTC and America/New_York.
Three possible states:
- Last run time is in the past at the expected time, but you never received output: The job is firing. The problem is in delivery or the model. Go to Root Cause 3 or 4.
- Last run time is in the past at an unexpected time: The job is firing at the wrong time. Timezone issue. Go to Root Cause 1.
- Last run time is null or much older than expected: The job is not firing. Gateway restart cleared the schedule or the schedule is misconfigured. Go to Root Cause 2.
Root cause 1: timezone mismatch
The most common reason a job appears to do nothing at the expected time is that it fired hours earlier or later, when you were not watching. The job worked , you missed it because the timezone was wrong.
The symptom is a last run time that is consistently offset from your expected time by a fixed number of hours. If you set the job to fire at 8am New York time and it consistently fires at noon, you have a 4-hour UTC offset problem (UTC-4 during daylight saving time).
Show me the schedule object for cron job [job-id]. Does it have a timezone set? If so, what is it? What time did it last fire in UTC and in America/New_York?
The fix is to update the schedule with the correct timezone:
Update cron job [job-id]: set the schedule timezone to America/New_York and keep the cron expression the same. Show me the next scheduled run time after the update to confirm it is correct.
Daylight saving time
If you omit the timezone and hardcode a UTC offset instead (e.g., UTC-5), your job will drift by one hour when daylight saving time changes. Use a named timezone like America/New_York and OpenClaw will handle the DST offset automatically.
Root cause 2: gateway restart cleared the schedule
When the OpenClaw gateway restarts, the cron scheduler reloads job definitions from config. In some versions, the next-fire-time for in-memory jobs is not persisted across restarts. The job definition survives but the scheduler loses track of when to fire it next.
The symptom is a job that has not fired since the last gateway restart. Check the restart time against the last run time:
When did the OpenClaw gateway last restart? When did cron job [job-id] last fire? Is the last fire time before the last restart time?
If the last fire time predates the last restart, the scheduler lost the job. The fix is a disable/re-enable cycle, which forces the scheduler to recalculate the next fire time from now:
Disable cron job [job-id], then immediately re-enable it. Show me the next scheduled run time after re-enabling.
If the next fire time now shows a future timestamp, the scheduler has picked it back up. It will fire at the next scheduled occurrence.
After any gateway restart
After any planned or unplanned gateway restart, run a cron job audit. List all jobs and verify their next fire times are in the future and correct. This takes two minutes and prevents hours of silent failures.
Root cause 3: delivery misconfigured
The job fired and the isolated agent completed the task, but the result was delivered somewhere you are not looking, or not delivered at all.
Common delivery failure scenarios:
- Wrong chat ID: The recipient ID in the job was entered incorrectly or belongs to a different account. The message delivered successfully , just not to you.
- Wrong channel: Delivery is set to
telegrambut you are checking Discord, or vice versa. - Delivery mode is none: The job was set to run silently and no output was intended.
- Webhook endpoint is down: The webhook URL in the delivery config is returning an error. The job ran; the POST failed.
Show me the full delivery config for cron job [job-id]. What channel is it delivering to? What is the recipient ID or URL? Does that match where I am expecting output?
To confirm delivery is working independently of the cron job:
Send a test message “delivery test” to my Telegram chat ID [your-chat-id] and confirm it arrived.
If the test message arrives but cron delivery does not, the job’s delivery config has an error. Update it:
Update cron job [job-id] delivery: set channel to telegram, set recipient to [your-chat-id], set mode to announce. Then trigger the job immediately to verify the delivery arrives correctly.
Root cause 4: model unavailable
The cron job fires on schedule, spawns an isolated session, but the isolated agent fails immediately because the model is not available. The failure is silent from your perspective because there is no channel to deliver the error message through , the session never got far enough to use the delivery config.
This happens when:
- The job uses a local Ollama model and Ollama is down or the model is not loaded.
- The job uses a cloud API and the provider is experiencing an outage or rate limit at the scheduled time.
- The model name in the job payload is misspelled or references a model that no longer exists.
What model is configured in cron job [job-id]? Is that model currently available? Check Ollama if it is a local model, or test with a simple API call if it is a cloud model.
Trigger cron job [job-id] immediately. Watch for any model-related errors in the output. If the job fails, show me the exact error message.
If the model is intermittently unavailable, add a fallback:
Update cron job [job-id]: the primary model should remain ollama/phi4:latest, but add deepseek/deepseek-chat as a fallback if the primary is unavailable.
Root cause 5: systemEvent target with idle session
If your job uses sessionTarget: "main" with payload.kind: "systemEvent", the event is injected into your main chat session as a system message. Your main agent is supposed to respond to it. But if your main session is idle or has been compacted, the behavior is unpredictable , the injection may queue, fire into a dormant context, or be processed by a session that has no awareness of your recent work.
The symptom is inconsistency: sometimes the job appears to work (when you happen to be active), and other times nothing happens (when the session is idle).
Show me the full job config for cron job [job-id]. Is it using sessionTarget: main with a systemEvent payload? If so, explain what happens to the event when the main session is idle.
The fix is to convert the job to an agentTurn payload with sessionTarget: "isolated". Isolated sessions are independent and do not require the main session to be active:
Update cron job [job-id]: change sessionTarget to “isolated” and change the payload kind to “agentTurn”. Keep the message text the same. Set delivery mode to announce with my Telegram chat ID as the recipient.
Why testing passes but production fails
Manual triggering and scheduled execution are not identical environments. When you trigger a job manually, several favorable conditions exist that do not apply to the scheduled run:
- You are active: Your session is running, Ollama is warm, the gateway just responded to your message. Everything is in a healthy state.
- The model is loaded: If you are using Ollama, the model is already in memory from your recent activity. It responds quickly. At 3am when the job fires on schedule, Ollama may be cold and the model may take 30+ seconds to load, pushing the job past its timeout.
- You are watching: When you trigger manually, you see errors immediately. When the job fires at 8am while you are asleep, a silent failure goes unnoticed until you check the job status.
- The timezone calculation happens at creation time: When you trigger manually, OpenClaw fires the job immediately regardless of timezone. The scheduled run depends entirely on the timezone being set correctly in the cron expression.
The practical implication is that testing a job once manually does not validate that it will work on schedule. You need to verify the last run time after the first scheduled execution, check that delivery arrived, and confirm the output matches what you saw during the manual test.
Validating a schedule after setup
After setting up a daily job, here is the minimum validation process before trusting it in production:
- Trigger the job manually and confirm the output looks correct and delivery arrives.
- Check the next scheduled run time and verify it matches your intention.
- After the first scheduled run, immediately check the last run time and confirm it fired when expected.
- Confirm the delivery arrived at the expected destination.
- If anything is off, fix it before the second scheduled run rather than letting it accumulate bad data.
I want to validate that cron job [job-id] is correctly configured. Check: enabled status, next fire time in America/New_York, model availability, delivery channel and recipient, and the last run result if available. Tell me if anything looks wrong.
Auditing all cron jobs at once
If you have multiple jobs and want to check the health of all of them in one pass:
List all my cron jobs. For each one, show: job ID, name, enabled status, next fire time in America/New_York, model, delivery mode and recipient, and last run result. Flag any job where the last run time is more than 25 hours ago for a daily job, or more than 8 days ago for a weekly job.
This single prompt gives you a full health picture. Run it after any gateway restart and weekly as a routine check.
Why cron failures are silent by design
When a scheduled openclaw cron job silent failure occurs, OpenClaw has no reliable channel to tell you about it. The delivery channel the job would have used is the same channel that would carry the failure notification. If the job failed before reaching the delivery step, there is nowhere for the error to go.
This is different from how failures work in your main session. In the main session, every error gets surfaced to you directly because you are the session. In an isolated cron session, the session exists, runs, and terminates without you present. Any error that occurs inside it either ends up in the job’s run history (which you have to check manually) or disappears entirely.
Understanding this changes how you monitor cron jobs. You cannot rely on being notified when something breaks. You have to pull the status yourself. The prompt below is your standard check:
List all my cron jobs. For each one, show the job ID, name, last run time, last run status (success or error), and the next scheduled run time. If any job has a last run status of error, show me the error message.
Run this after any gateway restart and any time a daily job you depend on does not produce output at the expected time.
Building failure visibility into the job itself
Since the cron system will not notify you when a job fails, you can build that notification directly into the job prompt. The technique is a try/report pattern: instruct the agent to attempt the task, and if anything fails, send a distinct failure message to your notification channel before exiting.
Here is what a health check job looks like with failure visibility built in:
Run: df -h and free -h. If either command fails, immediately send a Telegram message to [your-chat-id]: “CRON FAIL: health check could not run df or free , check Ollama and gateway status.” If the commands succeed, report normally: one line per metric, WARN prefix if above 80%.
The agent will execute the commands and, if anything goes wrong, send the failure message. You get notified of the failure through the same channel as normal output, using the agent’s own messaging tool rather than the job’s delivery config.
This pattern works because the agent inside the isolated session can still call message tools even when the job’s delivery config is misconfigured. It does not depend on the delivery layer at all.
Update the prompt for cron job [job-id]: add a failure reporting step at the beginning that sends a Telegram message to [your-chat-id] with the text “CRON ERROR: [job name] failed , [error]” if any step produces an error. Keep the rest of the prompt the same.
The Ollama cold-start problem
If your cron jobs use local Ollama models, be aware that Ollama may need to load the model into memory before it can respond. On a server with limited RAM, loading phi4:latest can take 30 to 60 seconds. If the cron job has a short timeout, it may expire before the model finishes loading.
The symptom is a job that works immediately after manual triggering (because you just used the model and it is warm) but fails or times out on schedule (because it fires in the middle of the night when the model has been unloaded).
Check the current status of Ollama. Is it running? Which models are currently loaded in memory? How much RAM is available on the server?
To keep the model warm, set OLLAMA_KEEP_ALIVE=-1 in your Ollama systemd service config. This keeps models in memory indefinitely rather than unloading them after a period of inactivity. The tradeoff is persistent RAM usage for each loaded model.
Check if OLLAMA_KEEP_ALIVE is set in my Ollama systemd service config. If not, show me how to set it to -1 to keep models loaded permanently.
RAM budget with permanent model loading
phi4:latest at Q4_K_M quantization uses approximately 8.5 GB of RAM. If your VPS has 16 GB, keeping phi4 loaded permanently leaves about 7.5 GB for OpenClaw and other processes. On a 8 GB server, phi4 alone may use most of the available RAM. Check available memory before setting OLLAMA_KEEP_ALIVE=-1.
Cron expression bugs that look like silent failures
A cron expression that parses correctly but fires less often than you expect will look like a silent failure between runs. Some common cron expression mistakes:
Day-of-month and day-of-week interaction
A cron expression like 0 8 1 * 1 (8am on the 1st of the month AND on Mondays) is valid but fires much less often than most people expect. In standard cron semantics, when both day-of-month and day-of-week are specified, the job fires when EITHER condition is true, not both. So this expression fires at 8am on every Monday AND also at 8am on the 1st of every month regardless of the weekday. This is usually not what was intended.
Explain what the cron expression “0 8 1 * 1” actually does in terms of when it fires each month. Is that the same as “8am on the first Monday of each month”? If not, what expression would achieve that?
Quartz vs. standard cron format
OpenClaw uses standard five-field cron format (minute hour day month weekday). Some tools use six-field Quartz format (second minute hour day month weekday). If you copied a cron expression from a tool that uses Quartz format, it will parse but fire at the wrong time.
Standard: 0 8 * * * (8am every day)
Quartz: 0 0 8 * * ? (also 8am every day, but six fields)
If your expression has six fields, it is Quartz format. Drop the first field (the seconds field) to convert it to standard five-field format.
Full recovery checklist
Run this in order when a cron job that worked before is suddenly producing nothing:
Step 1: Show me cron job [job-id] details , last run time, last run status, enabled status, next fire time in America/New_York.
Step 2: When did the OpenClaw gateway last restart? Is the last run time before the last restart?
Step 3: Is the model configured in cron job [job-id] currently available? Test with a simple prompt using that model.
Step 4: Trigger cron job [job-id] manually. Show me the full output including any errors. Did the delivery arrive?
Step 5: If the manual trigger worked but scheduled runs are not firing , disable and re-enable job [job-id], then confirm the next scheduled run time.
In most cases, one of these five steps will surface the problem. Steps 2 and 4 catch the majority of silent failures.
Preventing recurrence: making cron jobs self-healing
Diagnosing a silent failure once is fine. Diagnosing the same failure every time there is a gateway restart or a model hiccup is not. The goal is to build jobs that either heal themselves or alert you immediately when they cannot.
The disable/re-enable cron job
Create a separate daily cron job whose only purpose is to verify that your other important jobs have run in the last 24 hours. If any have not, it fires the missing job immediately and sends you an alert.
Create a cron job that runs every day at 9am America/New_York. Prompt: “Check cron job [job-id-1] and [job-id-2]. For each, verify the last run time is within the last 25 hours. If any has not run in the last 25 hours, trigger it immediately, then send a Telegram message: ‘CRON RECOVERY: [job name] was missed and has been re-triggered.’ If all jobs ran on time, send nothing.” Model: ollama/phi4:latest. Delivery: none.
This is a watchdog pattern. The watchdog job costs almost nothing to run and prevents silent failures from going unnoticed for days.
Stagger jobs after gateway restarts
The first few minutes after a gateway restart are when jobs are most likely to fail. The scheduler is reloading, Ollama models may be cold, and the gateway itself may still be initializing. If you have jobs scheduled in the first 5 to 10 minutes after a typical restart window, move them to fire later.
List all my cron jobs with their scheduled times. Are any of them scheduled to fire within 10 minutes of a typical gateway restart? If so, which ones and what would you recommend as safer times?
Use announce delivery for all production jobs
Set every production cron job to delivery.mode: "announce" pointing at your Telegram or Discord. Even if the output is something you would otherwise ignore (a clean health check, a routine summary), having the delivery arrive confirms the job ran. The absence of a message is the signal that something went wrong.
This is especially useful during the first few weeks after you set up a new job. Once you are confident the job runs reliably, you can switch it to conditional delivery or silence if the daily message becomes noise.
List all my cron jobs whose delivery mode is set to “none”. For any that are production jobs (not test jobs), update them to deliver to my Telegram with mode “announce”.
Using run history to reconstruct what happened
Every cron job maintains a run history. When you are trying to figure out whether a job fired last Tuesday and what it produced, the run history is the definitive record.
Show me the run history for cron job [job-id]. Include the time, status, and output summary for each run in the last 7 days.
What to look for in run history:
- Consistent success then sudden silence: Something changed externally. Gateway restart, model became unavailable, delivery token expired.
- Intermittent failures: Model availability issue or timeout. Ollama is sometimes cold, sometimes warm. Add OLLAMA_KEEP_ALIVE=-1 or switch to a cloud model.
- Consistent error with same message: Configuration problem that existed from the start. The job never actually worked in production , you only tested it manually.
- No history at all: The job was never enabled or the scheduler never loaded it. Check enabled status and do the disable/re-enable cycle.
Run history is available as long as the gateway has not been fully reset. For longer-term record keeping, build a logging step into the job prompt that writes a brief record to a dated file in your workspace.
Update cron job [job-id]: at the end of each run, append a one-line log entry to workspace/cron-logs/[job-name].log in the format: [timestamp] STATUS: [success/error] OUTPUT: [first 100 chars of output]. Create the file if it does not exist.
With a persistent log file, you can check whether a job ran even after a gateway restart that cleared the in-memory run history.
Edge cases that cause silent failures
Job defined but never enabled
A job can be created with enabled: false as a placeholder. It will never fire. If you created a job and forgot to enable it, it will look identical to a job that stopped working, except the enabled field in the job details will show false instead of true.
List all my cron jobs and flag any that have enabled set to false. For each disabled job, show me the name and schedule.
Job schedule is valid but fires far less often than expected
A schedule like 0 8 15 * * fires once per month, on the 15th. If you set this and expected a daily job, you will go 30 days without output before realizing the schedule was wrong. Always verify the next fire time after creating a job, and verify again 24 hours later that the second scheduled run occurred when expected.
Delivery channel token has expired
Telegram bot tokens and Discord bot tokens can be invalidated if the bot is regenerated or the token is rotated. If your cron job has been running for months and suddenly stops delivering, check whether the channel token is still valid.
Test my Telegram bot by sending a simple message to [your-chat-id]. If it fails, show me the error , I need to check whether the bot token has expired or been regenerated.
OpenClaw version update broke the cron scheduler
After an OpenClaw update, the cron scheduler behavior may have changed. If all your cron jobs stopped working simultaneously after an update, this is the most likely cause. Check the OpenClaw release notes for the version you updated to, looking for any mentions of cron or scheduler changes.
What version of OpenClaw is currently running? When was it last updated? Are there any known issues with the cron scheduler in recent versions?
If a version update broke your cron jobs, the standard recovery is the same disable/re-enable cycle, followed by a manual trigger to confirm the job functions correctly before waiting for the next scheduled run.
What a healthy cron setup looks like in practice
For reference, here is what a well-configured production cron setup produces when you run a status check:
- All jobs are enabled with future next-fire times that match the expected schedule in the correct timezone.
- All jobs have a last run time within the expected cadence (within 25 hours for daily jobs, within 8 days for weekly jobs).
- All last run statuses are success, not error.
- The model configured in each job is currently available and responds when tested.
- The delivery channel and recipient for each job matches an active channel where you will actually see the messages.
- Each job has failure-reporting logic in its prompt so that if a step breaks, you get a CRON FAIL message rather than silence.
Reaching this state does not require rebuilding your jobs from scratch. It requires one audit pass to confirm each item above, and then small fixes for anything that is off. Most cron setups can be brought to this state in under 30 minutes.
Audit all my cron jobs against this checklist: (1) enabled with future next-fire time, (2) last run within expected cadence, (3) last run status success, (4) model available, (5) delivery configured correctly. Report any job that fails any item, with the specific reason.
Run this audit now if you have not already. A job that appears to be working may have a delivery misconfiguration that will only surface when you actually need the output. Finding it during an audit is better than finding it when something urgent does not arrive. The whole audit takes two minutes and gives you a clean, verified state to build from. Scheduled automation that you can actually trust is worth the time to set up correctly.
Frequently asked questions
These questions cover edge cases that do not fit cleanly into the five root causes above but come up often when operators are debugging silent cron failures.
The job shows a last run time of exactly the expected time but I still got nothing. What now?
The job fired on time. Check the last run result field for that job , it will contain either the agent’s output or an error message. If the last run result shows an error, that error is your next diagnostic step. If it shows successful completion but you did not receive the delivery, the delivery configuration is the problem.
Can I get a notification when a cron job fails?
Not automatically from the cron system itself. The workaround is to include failure handling in the job prompt: instruct the agent to send a Telegram message if any step produces an error, using a distinct heading like “CRON FAILURE” so you can spot it immediately.
My job was working for weeks and then stopped. I did not change anything.
Three likely causes: a gateway restart cleared the schedule (most common), a model that was previously available is now unavailable, or a delivery endpoint changed (Telegram bot token rotated, Discord token expired, webhook URL changed). Start with the disable/re-enable cycle and a manual trigger to narrow it down.
How do I tell the difference between a job that ran and produced empty output vs. a job that did not run?
Check the last run time. If the timestamp is recent and matches your scheduled time, the job ran. Empty output means the agent produced nothing, which is a prompt or model problem. If the last run time is stale, the job did not run.
I set the cron job to every 5 minutes for testing. Now it is stuck on that schedule. How do I change it?
Update the schedule on the job directly. The job does not need to be deleted and recreated. Ask the agent to update the schedule to the correct cron expression for your production cadence, then confirm the next fire time.
The job fires correctly in one timezone but I moved to a different timezone. Do I need to update the job?
The job fires based on the timezone stored in its config, not your current location. If the stored timezone is correct for the city/region and you moved within the same timezone, nothing changes. If you moved to a different timezone and want the job to fire at the same local time in your new location, update the tz field in the schedule.
Is there a way to test delivery without waiting for the scheduled run?
Yes. Trigger the job manually with the test prompt below, then immediately check whether the delivery arrived. This is the fastest way to confirm that delivery is configured correctly before relying on the scheduled run.
Trigger cron job [job-id] immediately. After it completes, confirm whether the delivery arrived at [your expected destination].
I have two jobs with the same schedule. One works and one does not. What would cause that?
Compare the two jobs side by side , model, delivery config, session target, and prompt. The working job will reveal what the broken one is missing. The most common difference is a delivery recipient ID that was copied incorrectly on the second job.
