<?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: Nicodol</title>
    <description>The latest articles on DEV Community by Nicodol (@nicodol).</description>
    <link>https://dev.to/nicodol</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%2F4051562%2F7d475856-f39c-4160-828b-c3f2be7e18f8.jpg</url>
      <title>DEV Community: Nicodol</title>
      <link>https://dev.to/nicodol</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nicodol"/>
    <language>en</language>
    <item>
      <title>My 06:30 cron has never once started at 06:30</title>
      <dc:creator>Nicodol</dc:creator>
      <pubDate>Tue, 28 Jul 2026 14:37:22 +0000</pubDate>
      <link>https://dev.to/nicodol/my-0630-cron-has-never-once-started-at-0630-30me</link>
      <guid>https://dev.to/nicodol/my-0630-cron-has-never-once-started-at-0630-30me</guid>
      <description>&lt;p&gt;I keep a public repo whose only job is to watch how GitHub Actions treats &lt;code&gt;schedule:&lt;/code&gt; triggers. 18 workflows, a few deliberately pathological. The numbers below come from the boring ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five-minute job
&lt;/h2&gt;

&lt;p&gt;A workflow cron'd &lt;code&gt;*/5 * * * *&lt;/code&gt;, well within the documented limits. Last 7 days:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;97 runs out of ~2,016 slots. Five percent.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The daily job is the one that got me
&lt;/h2&gt;

&lt;p&gt;It's scheduled at &lt;code&gt;30 6 * * *&lt;/code&gt;. To GitHub's credit, it ran all seven days. It just never ran at 06:30.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Scheduled&lt;/th&gt;
&lt;th&gt;Actually started&lt;/th&gt;
&lt;th&gt;Late by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wed&lt;/td&gt;
&lt;td&gt;06:30&lt;/td&gt;
&lt;td&gt;09:09&lt;/td&gt;
&lt;td&gt;2h39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thu&lt;/td&gt;
&lt;td&gt;06:30&lt;/td&gt;
&lt;td&gt;09:07&lt;/td&gt;
&lt;td&gt;2h37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fri&lt;/td&gt;
&lt;td&gt;06:30&lt;/td&gt;
&lt;td&gt;09:03&lt;/td&gt;
&lt;td&gt;2h33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sat&lt;/td&gt;
&lt;td&gt;06:30&lt;/td&gt;
&lt;td&gt;08:29&lt;/td&gt;
&lt;td&gt;1h59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sun&lt;/td&gt;
&lt;td&gt;06:30&lt;/td&gt;
&lt;td&gt;08:56&lt;/td&gt;
&lt;td&gt;2h26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mon&lt;/td&gt;
&lt;td&gt;06:30&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10:26&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3h56&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tue&lt;/td&gt;
&lt;td&gt;06:30&lt;/td&gt;
&lt;td&gt;09:17&lt;/td&gt;
&lt;td&gt;2h47&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Average delay: &lt;strong&gt;2h42&lt;/strong&gt;. Best day of the week: two hours late. For a job whose entire identity is "runs at 06:30".&lt;/p&gt;

&lt;h2&gt;
  
  
  This is not a bug report
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;schedule:&lt;/code&gt; is documented as best-effort. My repo sits on the free plan where throttling bites hardest, and a busy repo full of crons is not a fair benchmark of your production org. Fine.&lt;/p&gt;

&lt;p&gt;What I can't accept is the silence.&lt;/p&gt;

&lt;p&gt;A run that &lt;strong&gt;fails&lt;/strong&gt; emails you. A run that &lt;strong&gt;never starts&lt;/strong&gt; is just an absence, and nothing anywhere is going to mention it. My daily job could have skipped Tuesday entirely and the only witness would have been the missing output.&lt;/p&gt;

&lt;h2&gt;
  
  
  It gets worse: GitHub turned one off
&lt;/h2&gt;

&lt;p&gt;During those test weeks, GitHub &lt;strong&gt;disabled&lt;/strong&gt; one of my scheduled workflows. Not slowed it down: turned it off, the way it does after 60 days of repo inactivity.&lt;/p&gt;

&lt;p&gt;No email. No banner. No event. I know the exact moment it happened because I was watching the API. If I hadn't been, that cron would simply have stopped existing.&lt;/p&gt;

&lt;h2&gt;
  
  
  I'm not alone, and it's getting worse
&lt;/h2&gt;

&lt;p&gt;There's a &lt;a href="https://github.com/orgs/community/discussions/156282" rel="noopener noreferrer"&gt;long community thread&lt;/a&gt; of people reporting scheduled runs 20 minutes to 2 hours late. A GitHub employee wrote in June:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the drift on the start of our scheduled jobs has got worse&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;...and that volume had grown over 30% in two months. In March, GitHub shipped &lt;code&gt;timezone:&lt;/code&gt; support for cron schedules. Useful, and it changes nothing here: it moves the moment a run is &lt;em&gt;owed&lt;/em&gt;, not the odds of getting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I built the watching half
&lt;/h2&gt;

&lt;p&gt;The workaround that thread converged on is an external scheduler calling &lt;code&gt;workflow_dispatch&lt;/code&gt;. In other words: stop trusting &lt;code&gt;schedule:&lt;/code&gt;. I built the other half of that idea instead, as a read-only GitHub App called &lt;strong&gt;Cronwake&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You install it once on an org. It finds every &lt;code&gt;cron:&lt;/code&gt; on its own and alerts you (Slack, Discord, Telegram, PagerDuty, webhook) when a run that should exist doesn't.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nothing added to your repos.&lt;/strong&gt; No YAML, no secrets, no ping step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No write access to anything.&lt;/strong&gt; Actions, Contents, Metadata: read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It waits before crying wolf.&lt;/strong&gt; A grace window, then reconfirmation against the API. GitHub being late is normal, and a tool that screams at every delay gets muted by Friday.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The silent disable above becomes an alert&lt;/strong&gt;, because it reads workflow state straight from the API. It also warns you &lt;em&gt;a week before&lt;/em&gt; the 60-day cutoff instead of after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A cron that has never fired even once is still covered.&lt;/strong&gt; Heartbeat-style monitors can't do that: they're waiting for a first ping that never comes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cronitor and Healthchecks are solid tools and I'm obviously not the first to monitor crons. My itch was narrower: no per-repo config file, no secret to distribute, install once and done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who watches the watcher
&lt;/h2&gt;

&lt;p&gt;Fair question for a monitoring tool. Cronwake runs outside GitHub on its own stack, with a &lt;a href="https://cronwake.com/status" rel="noopener noreferrer"&gt;public status page&lt;/a&gt;. A nightly job replays a week of production data against the detection model and fails the build if the two disagree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go look at your own numbers
&lt;/h2&gt;

&lt;p&gt;Open any scheduled workflow's run history and look at the start times this week. That's what convinced me.&lt;/p&gt;

&lt;p&gt;It's &lt;strong&gt;free while in beta&lt;/strong&gt;, and orgs that install now (and hook up an alert channel) keep the paid tier at half price for as long as they stay.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://cronwake.com" rel="noopener noreferrer"&gt;cronwake.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it misbehaves on your org, I genuinely want the bug report: &lt;a href="https://github.com/Nicodol/cronwake-feedback" rel="noopener noreferrer"&gt;github.com/Nicodol/cronwake-feedback&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>githubactions</category>
      <category>github</category>
      <category>monitoring</category>
    </item>
  </channel>
</rss>
