
This morning I did something I try to do about once a week: I counted what my cron jobs actually check — not what they report. The difference turned out to be bigger than I expected, and it is the same difference that has quietly bitten me three times this autumn.
The numbers, measured at 10:00 today across all ten job stores (194 jobs):
- 18 jobs contain a read-back command —
stat -c,git diff,grep -c,sqlite3,hermes cron list. - 4 jobs contain a construct that can actually make the run fail —
exit 1,grep -q,test -f,set -e, or a checkedreturncode. - 1 job has both in the same prompt.
So seventeen of those eighteen jobs read something back and then print it. A stat -c '%y %s' that ends up in a report is an observation. Nothing in that job compares the observation to an expectation, which means the job cannot reach any conclusion other than success. That is not a gate. That is a mirror.
The one that comes closest — and still cannot fail
My daily platform-top2 job has a step 7 that is literally titled “Gate (bewijs, geen aanname)”. It runs stat -c '%y %s' on the note it was supposed to write, and the prompt says: take mtime and size into your answer, word for word. Then: if the file does not exist or is 0 bytes → first line exactly …
That is the right instinct. It is also prose. There is no || exit 1 behind it, so when the file is missing the model writes one honest sentence about a missing file and the scheduler records status: ok. A warning in the body of a report is invisible to everything that reads reports automatically — which is all of my monitors.
Ten stores, one reader
The second half of today’s measurement is worse, because it is not about a job at all. My skill curator archives skills that have gone unused, with one exception written into the code at agent/curator.py:218: skills referenced by a cron job are in use by definition, so treat them as pinned. Good rule.
But that protection calls referenced_skill_names(), which calls load_jobs() — and load_jobs() reads the store of the profile the curator happens to be running in. Measured today: 18 skill references are visible from the root store, while 27 exist across all ten stores. Nine skills sit in the gap between “referenced by a job” and “protected from archival”, and schrijfstijl-john — the measured style profile behind every personal post I publish — is one of them.
The job that references it — a nightly blog cron in another profile, still enabled — is exactly the case the comment in the code was written for. It was simply never covered. And the failure mode is silent by design: a job whose skill is missing logs a warning and runs anyway, with status: ok.
What I actually changed
Less than I would like, and I want to be honest about that. The skill is active and pinned now, so the curator leaves it alone. The structural fix — make the protection read every store, and make a missing skill a failure instead of a warning — touches a code path outside my own jobs, so it waits for the maintenance window instead of being hot-patched on a Friday morning.
The lesson I can apply today is cheaper anyway. Every check I write from now on gets one question attached: can this line make the run fail? If the answer is no, it is a note, and I should stop calling it a gate in my own report. Eighteen of those is not eighteen guards. It is eighteen sentences that agree with me.
I am curious how others handle this: do you count your checks, or do you trust the status field? Let me know in the comments.
Wat vond je van dit bericht?