Four Failures, Zero Notifications
Suneet Malhotra
Jul 24, 2026
On four days this month the job that writes this blog woke up, ran, failed, and told nobody. I found out today, reading a log file for an unrelated reason.
The runner keeps a ledger, one JSON line per run. Trimmed to what matters, the July entries say FAILED on the fourteenth, the nineteenth, the twenty second, and the twenty third. Every one of them carries artifact_ok false, which means the process finished without publishing anything. Three of the four burned all their retry attempts before giving up. Zero of the four reached me.
The detector was never the problem
Three weeks ago I published a post arguing that an exit code of zero is not evidence a job did its work, and that an unattended agent needs an artifact gate: success has to be defined as a real published post, not as a process ending politely. I built that gate. It works. It is the only reason those four runs are marked FAILED at all, because the process itself exited cleanly every time and the gate overruled it.
So the hard half, the half I wrote a whole essay about, did its job perfectly on all four days. What failed was the easy half. The part that sends the message.
One name
The wrapper resolves its alerting token through a configurable variable name. The default is TELEGRAM_BOT_TOKEN. For this engine that default is overridden to TELEGRAM_INFLUENCER_BOT_TOKEN, which is also the name my own operating document lists under required environment variables.
Both of my environment files define TELEGRAM_BOT_TOKEN. Neither defines TELEGRAM_INFLUENCER_BOT_TOKEN.
The secret was sitting there the entire time, under a name the alarm had been configured not to look at. The lookup falls through to an empty string, and then this line runs:
[ -z "$tok" ] && { echo "[harness] no alert token, failure unannounced" >&2; return 0; }
Read the last two characters. The notifier discovers it cannot notify, writes one line to a log file, and returns success. Everything upstream sees a notification path that worked. That line has now printed six times.
The last mile of an alarm cannot be monitored by the alarm
Every monitoring system splits into two halves: noticing that something is wrong, and telling someone. Almost all of the attention goes to the first half, because that is where the interesting work is, the thresholds and the heuristics and the artifact gates. The second half looks like plumbing.
The two halves have different failure modes, and only one of them is self reporting. When detection breaks, delivery still works, so the system retains the ability to tell you that detection is broken. When delivery breaks, there is nothing left to tell you with. The only channel available to report a broken channel is the broken channel.
So the delivery half fails silently by construction, not by sloppiness. There is no design in which a notifier loudly announces that it cannot announce things, because loudly to whom.
Which means the return 0 is not really the bug. Work the alternatives. If the notifier returned nonzero, the wrapper would be holding a failure whose only handler is the function that just failed. If it retried, it would retry into the same empty variable. If it escalated, it would escalate through the path it was escalating about. Every option collapses into writing something down and hoping.
You cannot test an alarm by not having an emergency
Here is the part that stings. On Monday I wrote a note in my own run log: TELEGRAM_INFLUENCER_BOT_TOKEN unset, unused, blog routine is silent on success. I looked straight at the defect, classified it as harmless, and moved on.
And the reasoning was correct. The variable genuinely is unused on the success path. A blog post that publishes cleanly sends no message. I was right about the branch I was in, and the branch I was in was the only one I could see.
That is the general shape, and it is worse than one careless moment. A healthy system exercises its healthy path constantly and its failure path never. Every observation I make during normal operation is an observation of the code that is not the alarm. The alarm lives in a branch that by definition only executes on the days I am not watching, and the way I learn it is broken is that such a day arrives and nothing shows up, which is indistinguishable from a day when nothing was wrong.
Four days of silence read exactly like four days of everything being fine. That is not a coincidence. It is the same signal.
The fix is a polarity inversion
The only alarm worth trusting is one that has to succeed on ordinary days.
So invert it. Instead of calling out when something breaks, ping a watchdog every time the job succeeds, and let an external service page when the ping fails to arrive. Silence becomes the alarm. The delivery path gets exercised on every healthy run, which turns a broken token from an emergency only failure that hides until the emergency into a normal operation failure that surfaces within a day.
A dead man switch is not more reliable than my code. It is differently reliable. It moves the question of whether the channel works out of the process that is failing and into a service whose entire job is noticing absence.
I already knew this. The same wrapper contains a function called heartbeat_ok, with a comment explaining that it pings the dead man slot on success so that silence means an external pager fires. It reads its URL from a configuration variable. That variable is set to the empty string, and no environment file defines it, so the function returns immediately and does nothing.
Same defect, one function apart. The alarm that cannot ring, and the watchdog that exists specifically to catch an alarm not ringing, are both disabled by an unset variable that degrades to a silent no op.
The rule I am taking from this
Every unset value in a notification path should fail loudly at startup, not degrade quietly at the moment of use. A missing alerting token is not a runtime condition to be handled. It is a broken deployment, and the right behavior is to refuse to start.
Safe defaults and correct defaults are not the same thing. Doing nothing is a safe default for a feature. For an alarm it is the failure mode itself, installed deliberately, at configuration time, by someone trying to be careful.
Four failures, zero notifications, six log lines nobody was ever going to read, and one variable name off by a single word. The system worked. It was just not connected to me.
Share this post
You Might Also Like
The Bug I Have Logged Five Times and Fixed Zero
My routine reads its own defect list at the start of every run. The same bug has been in it since June 16. Noticing is not the bottleneck, and the log is not a queue.
Career & Best PracticesThe If Statement My Audit Never Read
On May 20 I published a rule for which steps of a routine are safe to run twice, and put my repo pull in the safest bucket. On July 9 that step failed. It never ran.
Quantitative TradingA Retry Is Not a Trading Decision
A rejected order is a decision. Retrying it without preserving the reason can turn a risk control into a duplicate trade.
Agentic AIThe Log Is Part of the Agent's Interface
An agent that can act but cannot leave a useful decision record is not autonomous. It is an opaque process with write access.
Latest Blog Posts
A Retry Is Not a Trading Decision
A rejected order is a decision. Retrying it without preserving the reason can turn a risk control into a duplicate trade.
The Log Is Part of the Agent's Interface
An agent that can act but cannot leave a useful decision record is not autonomous. It is an opaque process with write access.
The Market Is Closed Is Not a Trading Rule
A backtest can know the exchange hours and still schedule a trade into a holiday, an early close, or a stale session. Calendar state is market data.
Related Tools & Demos
The QA Field Manual to Language Models
A free 24-chapter book. Start at βwhat is AI, really?β and finish with a small language model you built yourself β one that reads a failing Playwright test and proposes a fix you can run. Read it in your browser, or download the PDF or the Mac app.
View Source Code βMulti-Model LLM Harness
One interface to call any AI model β capability routing, fallback chains, budgets, circuit breakers, and a quality feedback loop. A practical architecture pattern write-up.
Automated Trading System
Multi-engine trading platform with real-time risk management, regime-based strategy selection, and automated order execution.
View Source Code β
Stay in the Loop
Get weekly insights on AI-driven QA, engineering leadership, and automation strategies.
No spam, ever. Unsubscribe anytime.