<?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: ymj</title>
    <description>The latest articles on DEV Community by ymj (@ing7919).</description>
    <link>https://dev.to/ing7919</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%2F4051682%2F1e0ebead-fc04-424a-befc-87bdcaf222b5.png</url>
      <title>DEV Community: ymj</title>
      <link>https://dev.to/ing7919</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ing7919"/>
    <language>en</language>
    <item>
      <title>Your cron job exited 0. That doesn't mean it worked.</title>
      <dc:creator>ymj</dc:creator>
      <pubDate>Tue, 28 Jul 2026 16:12:41 +0000</pubDate>
      <link>https://dev.to/ing7919/your-cron-job-exited-0-that-doesnt-mean-it-worked-85o</link>
      <guid>https://dev.to/ing7919/your-cron-job-exited-0-that-doesnt-mean-it-worked-85o</guid>
      <description>&lt;p&gt;My trading bot liquidated an entire position because of a number &lt;br&gt;
that was technically valid.&lt;/p&gt;

&lt;p&gt;The exchange API returned a balance of 0. Not an error, not a &lt;br&gt;
timeout — a clean &lt;code&gt;200&lt;/code&gt; with a well-formed body. My bot read it, &lt;br&gt;
calculated a 100% drawdown, and did exactly what I had told it to &lt;br&gt;
do in that situation: close everything.&lt;/p&gt;

&lt;p&gt;Nothing errored. No exception, no non-zero exit, clean logs. My &lt;br&gt;
monitoring said the job ran on schedule.&lt;/p&gt;

&lt;p&gt;It just ran on garbage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap between "ran" and "worked"
&lt;/h2&gt;

&lt;p&gt;Most job monitoring answers one question: &lt;em&gt;did the process report &lt;br&gt;
back?&lt;/em&gt; That catches the loud failures — crashes, timeouts, dead &lt;br&gt;
containers. Those are the easy ones, because something screams.&lt;/p&gt;

&lt;p&gt;It does not catch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A query that returned 0 rows because an upstream table was empty&lt;/li&gt;
&lt;li&gt;A backup that wrote a truncated file and uploaded it successfully&lt;/li&gt;
&lt;li&gt;A parser that silently skipped every row after a schema change&lt;/li&gt;
&lt;li&gt;An API returning a valid-looking default instead of real data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these exits 0. Every one pings your monitor. Every one &lt;br&gt;
is wrong.&lt;/p&gt;

&lt;p&gt;The absence of an error is not the presence of success. We conflate &lt;br&gt;
them constantly because, most of the time, they line up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do now
&lt;/h2&gt;

&lt;p&gt;I stopped asking "did it run" and started asking "does the result &lt;br&gt;
look like a result." At the end of each run I emit a few numbers &lt;br&gt;
and compare them to previous runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Row count against a baseline&lt;/strong&gt; — not a fixed threshold, a 
comparison to the recent average. A job that normally writes 15,000 
rows and suddenly writes 400 is interesting even though 400 is a 
legal number. Fixed thresholds rot; nobody re-tunes them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value ranges&lt;/strong&gt; — a balance of exactly 0, a price of 0, a date 
in 1970. Valid types, implausible values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema hash&lt;/strong&gt; — if the column names or order change, I want to 
know before the data does something strange, not after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output hash vs. the previous run&lt;/strong&gt; — if today's output is 
byte-identical to yesterday's, either nothing happened or I'm 
reading a stale source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No warehouse, no pipeline framework. It's a dictionary of numbers &lt;br&gt;
at the end of a script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'm still stuck
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Where the check belongs.&lt;/strong&gt; Right now it lives inside each script, &lt;br&gt;
so every script re-implements a slightly different version of the &lt;br&gt;
same logic — and the check dies with the process it's supposed to &lt;br&gt;
be checking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proving it afterward.&lt;/strong&gt; Knowing a run was fine is one thing. &lt;br&gt;
Demonstrating later that a specific output came from a specific run &lt;br&gt;
and hasn't been modified since is another. Log lines are trivially &lt;br&gt;
editable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd like to know
&lt;/h2&gt;

&lt;p&gt;If you run scheduled jobs in production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do you verify the output, or only that the job finished?&lt;/li&gt;
&lt;li&gt;Where does that check live — in the script, a separate step, or 
your monitoring tool?&lt;/li&gt;
&lt;li&gt;If you've hit a "succeeded but wrong" case, how long did it take 
you to notice?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'm still patching this together myself, so I'm more curious what &lt;br&gt;
people actually do than what the ideal setup looks like.&lt;/p&gt;

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