<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: C&amp;T DIGITAL COMMERCE E TECNOLOGIA LTDA</title>
    <description>The latest articles on DEV Community by C&amp;T DIGITAL COMMERCE E TECNOLOGIA LTDA (@ct_digitalcommerceete).</description>
    <link>https://dev.to/ct_digitalcommerceete</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4242647%2F46547f51-df8e-48bb-96db-98bda661e9dd.jpg</url>
      <title>DEV Community: C&amp;T DIGITAL COMMERCE E TECNOLOGIA LTDA</title>
      <link>https://dev.to/ct_digitalcommerceete</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ct_digitalcommerceete"/>
    <language>en</language>
    <item>
      <title>Your OpenClaw cron stopped and nobody told you</title>
      <dc:creator>C&amp;T DIGITAL COMMERCE E TECNOLOGIA LTDA</dc:creator>
      <pubDate>Fri, 13 Nov 2026 09:55:12 +0000</pubDate>
      <link>https://dev.to/ct_digitalcommerceete/your-openclaw-cron-stopped-and-nobody-told-you-20m6</link>
      <guid>https://dev.to/ct_digitalcommerceete/your-openclaw-cron-stopped-and-nobody-told-you-20m6</guid>
      <description>&lt;p&gt;You set up an OpenClaw automation to send a report every night at 2 AM, and it works for a week. Then the host reboots for an update, or the laptop running the Gateway goes to sleep, or the process dies on some Tuesday. The report stops arriving.&lt;/p&gt;

&lt;p&gt;Nobody tells you, because the thing that would have told you is down too.&lt;/p&gt;

&lt;p&gt;This isn't a bug in OpenClaw. It's how most self-hosted schedulers work, and the OpenClaw docs say so plainly: automations run inside the Gateway process, and the Gateway has to be running for schedules to fire. Failure notifications come from that same Gateway. If the Gateway is down, the job doesn't run and the alert doesn't run either.&lt;/p&gt;

&lt;p&gt;The scheduler can't also be the watchdog. Here's how I fixed that, with a setup I tested against a real Gateway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways a scheduled job fails
&lt;/h2&gt;

&lt;p&gt;When a scheduled job breaks, it breaks in one of three ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It fails.&lt;/strong&gt; The script runs and exits non-zero. OpenClaw records an error, and you see it if you happen to look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It hangs.&lt;/strong&gt; It starts and never finishes: a stuck API call, a lock, a model turn that never returns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It never runs.&lt;/strong&gt; Gateway down, machine asleep, bad deploy. Nothing happens, so there's nothing to log.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third one is the dangerous one. Anything inside the box can catch the first one, but no amount of in-process logging catches the third.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flip it: the job has to prove it ran
&lt;/h2&gt;

&lt;p&gt;The fix is old and boring, which is good. It's called a dead man's switch, or heartbeat monitoring. The job calls a URL when it starts and again when it finishes. A service outside your machine knows when to expect that call. If the call doesn't arrive on time, that service alerts you.&lt;/p&gt;

&lt;p&gt;So silence becomes the signal. If the Gateway is down, the job can't call home, and that missing call is what triggers the alert.&lt;/p&gt;

&lt;p&gt;You need three endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/start&lt;/code&gt; when the job begins (this is how hangs get caught)&lt;/li&gt;
&lt;li&gt;the bare URL on success&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/fail&lt;/code&gt; with the exit code on failure (an alert right away, instead of waiting for the timeout)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The wrapper script
&lt;/h2&gt;

&lt;p&gt;Wrap the real job in a small shell script that sends the signals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c"&gt;# ~/jobs/nightly-report.sh   (chmod +x)&lt;/span&gt;
&lt;span class="nv"&gt;CO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://cronobserver.com/checkin/${CRONOBSERVER_TOKEN:?set CRONOBSERVER_TOKEN}"&lt;/span&gt;

curl -fsS -m 10 --retry 3 -o /dev/null -X POST &lt;span class="s2"&gt;"$CO/start"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; python3 &lt;span class="s2"&gt;"$HOME/jobs/nightly_report.py"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;   &lt;span class="c"&gt;# your job&lt;/span&gt;
  curl -fsS -m 10 --retry 3 -o /dev/null -X POST &lt;span class="s2"&gt;"$CO"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; NO_REPLY                     &lt;span class="c"&gt;# nothing posted to chat on success&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="nv"&gt;rc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
  curl -fsS -m 10 --retry 3 -o /dev/null -X POST &lt;span class="s2"&gt;"$CO/fail"&lt;/span&gt; \
    -H &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; \
    -d &lt;span class="s2"&gt;"{\"exitCode\":$rc,\"message\":\"nightly_report.py failed\"}"&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;"$rc"&lt;/span&gt;                        &lt;span class="c"&gt;# OpenClaw records the run as error&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two OpenClaw details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NO_REPLY&lt;/code&gt; on stdout tells OpenClaw there's nothing to post, so a successful run doesn't spam your chat.&lt;/li&gt;
&lt;li&gt;The script exits with the job's own exit code, so OpenClaw's run history stays honest. The run shows up as ok or error on both sides.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Schedule it as a command automation
&lt;/h2&gt;

&lt;p&gt;A command payload runs the script on the Gateway host with no model turn. You don't spend tokens to run a cron job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw cron create &lt;span class="s2"&gt;"0 2 * * *"&lt;/span&gt; --name nightly-report \
  --command &lt;span class="s2"&gt;"$HOME/jobs/nightly-report.sh"&lt;/span&gt; \
  --command-env &lt;span class="nv"&gt;CRONOBSERVER_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&amp;lt;&lt;/span&gt;token&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; \
  --no-deliver

&lt;span class="c"&gt;# Run it once now and watch the check-ins arrive&lt;/span&gt;
openclaw cron run &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;job-id&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
openclaw cron runs --id &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;job-id&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the monitoring side, use the same schedule (&lt;code&gt;0 2 * * *&lt;/code&gt;, same time zone), a grace period for normal delays (I use 10 minutes) and a max runtime longer than the job's slowest normal run (30 minutes here).&lt;/p&gt;

&lt;h2&gt;
  
  
  Two gotchas I hit while testing
&lt;/h2&gt;

&lt;p&gt;I ran this on OpenClaw 2026.11.3 with a real Gateway before writing about it. Start, success and fail all arrived. I also hit two gotchas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;--command-env&lt;/code&gt; is stored in the job definition and run history.&lt;/strong&gt; The token above can only send check-ins, so leaking it isn't catastrophic, but I still don't like secrets sitting in history. Read it from a file inside the script instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CRONOBSERVER_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;cat &lt;span class="s2"&gt;"$HOME/.config/cronobserver/token"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. OpenClaw's &lt;code&gt;--timeout-seconds&lt;/code&gt; kills the script before it can send &lt;code&gt;/fail&lt;/code&gt;.&lt;/strong&gt; If you set a timeout and the job runs past it, OpenClaw kills the process and the failure branch never runs. That's why the max runtime matters: &lt;code&gt;/start&lt;/code&gt; arrived, nothing came after it, so the monitor flags the run as hung anyway. When I tested this with a 60-second max runtime, the run was marked as exceeded and the alert went out.&lt;/p&gt;

&lt;h2&gt;
  
  
  It works for agent jobs too
&lt;/h2&gt;

&lt;p&gt;Nothing here is specific to Python. If your nightly job is an agent prompt, call your agent's CLI where the script calls &lt;code&gt;nightly_report.py&lt;/code&gt;. The wrapper doesn't care what runs inside it, only how it exits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Failed:&lt;/strong&gt; &lt;code&gt;/fail&lt;/code&gt; arrives with the exit code and message, and you're alerted immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hung:&lt;/strong&gt; &lt;code&gt;/start&lt;/code&gt; arrived, but nothing followed within the max runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never ran:&lt;/strong&gt; nothing arrived by the scheduled time plus the grace period. This is the Gateway-down case, and the reason to do any of this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovered:&lt;/strong&gt; the next successful check-in closes the incident.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I built &lt;a href="https://cronobserver.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=growth-2026-11&amp;amp;utm_content=openclaw" rel="noopener noreferrer"&gt;CronObserver&lt;/a&gt;, the heartbeat monitor in the URLs above. The pattern works with any heartbeat endpoint, including one you host yourself, as long as it runs somewhere other than the box it watches. If you want the tested OpenClaw version step by step, it's here: &lt;a href="https://cronobserver.com/integrations/openclaw?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=growth-2026-11&amp;amp;utm_content=openclaw" rel="noopener noreferrer"&gt;cronobserver.com/integrations/openclaw&lt;/a&gt;. CronObserver isn't affiliated with the OpenClaw project.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>ai</category>
      <category>devops</category>
      <category>monitoring</category>
    </item>
  </channel>
</rss>
