<?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: Pytheas</title>
    <description>The latest articles on DEV Community by Pytheas (@pytheas).</description>
    <link>https://dev.to/pytheas</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3869950%2F242ea7f3-44fa-4fb1-9eea-c7c54a8310d3.png</url>
      <title>DEV Community: Pytheas</title>
      <link>https://dev.to/pytheas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pytheas"/>
    <language>en</language>
    <item>
      <title>I built an MCP server that lets Claude debug failed cron jobs</title>
      <dc:creator>Pytheas</dc:creator>
      <pubDate>Tue, 14 Apr 2026 09:13:41 +0000</pubDate>
      <link>https://dev.to/pytheas/i-built-an-mcp-server-that-lets-claude-debug-failed-cron-jobs-3djb</link>
      <guid>https://dev.to/pytheas/i-built-an-mcp-server-that-lets-claude-debug-failed-cron-jobs-3djb</guid>
      <description>&lt;p&gt;I built &lt;a href="https://cronsignal.io" rel="noopener noreferrer"&gt;CronSignal&lt;/a&gt;, a cron job monitoring service. Recently I added an &lt;a href="https://www.npmjs.com/package/cronsignal-mcp" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt; so AI tools like Claude Code and Cursor can interact with it directly.&lt;/p&gt;

&lt;p&gt;Here's what it does and why I think it's worth setting up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The debugging loop it replaces
&lt;/h2&gt;

&lt;p&gt;When a cron job fails, the usual flow is: open dashboard, see what's down, SSH into the server, read logs, figure out the problem, fix it. Lots of tab switching.&lt;/p&gt;

&lt;p&gt;With the MCP server, you can do most of that from the terminal. Claude has access to your monitors and can pull diagnostics and job output on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like in practice
&lt;/h2&gt;

&lt;p&gt;The MCP server exposes a few tools: &lt;code&gt;list_checks&lt;/code&gt;, &lt;code&gt;create_check&lt;/code&gt;, &lt;code&gt;diagnose_check&lt;/code&gt;, &lt;code&gt;get_check_output&lt;/code&gt;, &lt;code&gt;pause_check&lt;/code&gt;, &lt;code&gt;resume_check&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosing a failure:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The db-backup check is showing as down, what's going on?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude calls &lt;code&gt;diagnose_check&lt;/code&gt; and &lt;code&gt;get_check_output&lt;/code&gt;, pulls the stderr from the last run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pg_dump: error: connection to server failed: FATAL:
password authentication failed for user "backup_user"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells you the credentials are wrong without you having to SSH anywhere. CronSignal captures up to 100KB of stdout/stderr per run, so the error is usually right there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating a monitor:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Set up a monitor for a deploy script that runs every day at 2am"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude calls &lt;code&gt;create_check&lt;/code&gt; and gives you the curl to add to your &lt;a href="https://cronsignal.io/tools/cron-generator" rel="noopener noreferrer"&gt;crontab&lt;/a&gt;. No dashboard needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checking status:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How are my cron jobs looking?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lists all monitors with current status. Faster than opening a browser tab.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bulk operations:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Pause the staging monitors, I'm doing a migration"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One sentence instead of clicking through each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful part: Claude sees your code AND the error
&lt;/h2&gt;

&lt;p&gt;The thing that makes this better than just reading a dashboard is that Claude has context on both sides. It can see the error output from the failed job AND the code that produced it. So instead of just showing you "connection refused," it can tell you why — misconfigured env var, wrong port, whatever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Install the MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cronsignal-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to your Claude Code or Cursor config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cronsignal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cronsignal-mcp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"CRONSIGNAL_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cs_live_..."&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;API key is at &lt;a href="https://cronsignal.io" rel="noopener noreferrer"&gt;cronsignal.io&lt;/a&gt; → Settings → API Keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is CronSignal
&lt;/h2&gt;

&lt;p&gt;If you haven't seen it — CronSignal monitors cron jobs using heartbeat pings. You add a curl after your command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /scripts/backup.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; https://api.cronsignal.io/ping/YOUR_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the ping doesn't arrive on time, you get alerted via email, Slack, Discord, Telegram, or webhook. &lt;a href="https://cronsignal.io" rel="noopener noreferrer"&gt;Free tier&lt;/a&gt; has 3 monitors. Pro is $5/month for unlimited.&lt;/p&gt;

&lt;p&gt;The MCP server is a layer on top — it gives AI tools access to the same API you'd normally use through the dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cronsignal.io" rel="noopener noreferrer"&gt;CronSignal&lt;/a&gt; — the monitoring service&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.npmjs.com/package/cronsignal-mcp" rel="noopener noreferrer"&gt;cronsignal-mcp on npm&lt;/a&gt; — the MCP server&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cronsignal.io/docs/mcp" rel="noopener noreferrer"&gt;MCP docs&lt;/a&gt; — setup and available tools&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>monitoring</category>
      <category>claude</category>
    </item>
    <item>
      <title>Your Cron Jobs Are Silently Failing. Here's How to Know in 30 Seconds.</title>
      <dc:creator>Pytheas</dc:creator>
      <pubDate>Tue, 14 Apr 2026 09:00:58 +0000</pubDate>
      <link>https://dev.to/pytheas/your-cron-jobs-are-silently-failing-heres-how-to-know-in-30-seconds-48ic</link>
      <guid>https://dev.to/pytheas/your-cron-jobs-are-silently-failing-heres-how-to-know-in-30-seconds-48ic</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"My database backup script broke 11 days before I found out. Credentials got rotated, pg_dump started erroring, and cron just kept running it on schedule like nothing was wrong. No email. No alert. Eleven days of no backups. I only found out because I needed to restore something."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sound familiar? If you've run cron jobs in production, you've probably been here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cron doesn't know your job failed
&lt;/h2&gt;

&lt;p&gt;This is the part that gets people. Cron's job is to start your command at the time you told it to. That's it. If the command exits 1, cron doesn't care. If it hangs forever, cron doesn't care. If the server reboots and the cron daemon doesn't come back up, nobody cares.&lt;/p&gt;

&lt;p&gt;You find out when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A customer asks why their data is stale&lt;/li&gt;
&lt;li&gt;A queue fills up because the consumer job stopped&lt;/li&gt;
&lt;li&gt;You manually check a dashboard and notice the last run was 9 days ago&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fix is one line
&lt;/h2&gt;

&lt;p&gt;After your job completes successfully, ping an external URL. If the ping stops arriving, you get alerted. That's the whole idea.&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;# before&lt;/span&gt;
0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /scripts/backup-db.sh

&lt;span class="c"&gt;# after&lt;/span&gt;
0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /scripts/backup-db.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; https://cronsignal.io/ping/abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; means the curl only fires if the script exits 0. Script fails? No ping. Script hangs? No ping. Server goes down? No ping. In all cases, you hear about it.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://cronsignal.io" rel="noopener noreferrer"&gt;CronSignal&lt;/a&gt; for this because I wanted something stupid simple. You create a check, tell it how often to expect a ping, and add the curl. If the ping is late, it hits you on email, Slack, Discord, Telegram, or webhook. Setup is maybe 30 seconds.&lt;/p&gt;

&lt;p&gt;3 monitors are free. If you need more, it's $5/month flat for unlimited. No per-monitor pricing nonsense.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions has its own version of this problem
&lt;/h2&gt;

&lt;p&gt;If you use &lt;code&gt;schedule&lt;/code&gt; triggers in GitHub Actions, you've probably noticed they're... unreliable. GitHub can delay scheduled runs by minutes or hours. If your repo goes 60 days without a push, GitHub silently disables the schedule entirely. No warning.&lt;/p&gt;

&lt;p&gt;I made a &lt;a href="https://github.com/CronSignal/ping-action" rel="noopener noreferrer"&gt;GitHub Action&lt;/a&gt; for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Nightly Build&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ping CronSignal&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;success()&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CronSignal/ping-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;check-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CRONSIGNAL_CHECK_ID }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the workflow gets delayed, skipped, or disabled, you know about it the same day instead of weeks later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Works with basically anything
&lt;/h2&gt;

&lt;p&gt;The curl pattern works anywhere. Not just crontab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;systemd timers&lt;/strong&gt; — &lt;code&gt;ExecStartPost&lt;/code&gt; directive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes CronJobs&lt;/strong&gt; — final container step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laravel&lt;/strong&gt; — &lt;code&gt;$schedule-&amp;gt;command('...')-&amp;gt;after(function() { Http::get('...'); })&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Django/Celery&lt;/strong&gt; — &lt;code&gt;requests.get()&lt;/code&gt; at the end of your task&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node&lt;/strong&gt; — &lt;code&gt;fetch()&lt;/code&gt; call after your logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point is: don't monitor the scheduler. Monitor whether the job actually finished. Those are different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anyway
&lt;/h2&gt;

&lt;p&gt;If you're running cron jobs without monitoring, you're going to have a bad time eventually. The fix is one curl command and 30 seconds of setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cronsignal.io" rel="noopener noreferrer"&gt;CronSignal&lt;/a&gt; if you want to try it. Or use any heartbeat monitoring service — Healthchecks.io, Dead Man's Snitch, whatever. Just use something. The &lt;code&gt;&amp;amp;&amp;amp; curl&lt;/code&gt; pattern works the same regardless.&lt;/p&gt;

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