AI systems · Automation reliability

Silent automation failures: how four of our own systems broke while reporting success

Exit code zero means the process ran. It does not mean the work happened. We learned the difference four times in one week, on our own systems.

WritingBy Landon LittleAugust 19, 20266 min read

The embarrassing part first

Our stack runs on automations we built ourselves: prospect list refueling, cold email, social posting, backend health checks, a morning briefing. For months, the monitoring on all of it was me occasionally remembering to check whether something had happened lately. The audit behind this post started with me admitting, in writing, that the only way I found out about broken automations was going and looking.

We suspect most small-business automation is monitored the same way. Somebody wires a workflow, watches it succeed twice, and walks away. From that day on, the system reports whatever its happiest code path reports, and the owner finds out it died whenever they next happen to wonder about it.

Four failures, zero alerts

The 2026 audit of our own stack. Every row looked healthy from the outside.
SystemQuiet forWhat it reportedWhat was actually true
Cold email queue20 daysQueue empty, clean exitA half-finished config change pointed the list builder at a file the sender had retired. About 130 verified prospects piled up in a file nothing read.
LinkedIn posting18 daysOffline, skipping runThe connectivity check ran under a Python install with no certificate bundle. Every HTTPS probe failed certificate verification and a catch-all handler relabeled it offline.
Backend failure alerts7+ weeksNothing, by designSlack alerting for scheduled-job failures sat behind an environment flag nobody had set. A second, independent mute flag was stacked on top of it.
Three scheduled scriptsIntermittentSuccess, some daysUnder the scheduler's minimal environment, plain python3 sometimes resolved to a different interpreter without file access. Same script, different interpreter, different day.

Every row shares the same shape. The process ran. The process exited cleanly. The process was wrong about the one thing it exists to do.

The LinkedIn failure deserves a closer look, because it was built with good intentions. After a real outage, we added a connectivity check so the poster would skip runs while the network was down. That check happened to execute under a Python installation with no certificate authority bundle wired in, so every single HTTPS probe failed certificate verification. A broad exception handler caught the error and reported the network as offline. The safety check could never once pass. A guard that cannot distinguish the network is down from I am misconfigured will spend its life reporting the flattering one.

The cold-email failure was even smaller: one line, half finished. The script that promotes verified prospects was updated to write to a new file one evening, and the sender that reads the queue was not updated to match. Both jobs kept running daily. Both kept exiting cleanly. One filled a file nothing read, the other read a file nothing filled and logged queue empty, for 20 days. Two healthy-looking automations, connected by a filename, doing nothing.

Why silent is the default, not the exception

None of this is exotic, and none of it required unusual bad luck. Error handling gets written for the errors the author imagined, so the surprise arrives as a calm log line instead of a page. Schedulers know whether a process ran, not whether the work happened. Alert channels ship behind flags, and flags default to off. Each of these is a reasonable engineering choice on its own. Stacked together, they guarantee that the failure you did not imagine is exactly the one nobody hears about.

The deeper problem is that no errors and working are different claims. A sender with an empty queue has no errors. A poster that believes it is offline has no errors. A muted alert system has, by definition, no errors. If your definition of healthy is the absence of red, a system can be dead for a month and stay green the whole time. Ours did.

The proof-of-life rule we build to now

Everything we wire now has to carry a signal tied to the outcome, not the process. The specific fixes from the audit took an afternoon each. The rules they left behind:

  • Assert the outcome, not the exit code. The email sender now checks the delivery count it actually achieved against the queue it believed it had. Zero sends on a day the queue says otherwise is an alert, not a log line.
  • Alert on quiet, not just on error. If a system that normally does something daily does nothing for days, that silence should page a human, because silence is exactly what the worst failures produce.
  • Un-mute by default. Alert switches that need a flag turned on will spend part of their life off without anyone having decided that. We found two of those stacked on one system.
  • Pin the environment. Scheduled jobs get explicit interpreter paths and explicit certificate bundles. Whatever the environment resolves to is a different program on different days.
  • Read the whole log once. The tail shows the weather. The full log shows the climate.

What to ask about your own automations

If someone built automation for you, or you built it yourself, you can audit it without reading a line of code. Ask three questions of each automation you depend on:

  • What number should move when this works, and where do I see it? If the answer is a log file, that is a no.
  • Who finds out, and how fast, if that number stops moving?
  • When did a human last confirm the outcome end to end, instead of the dashboard saying fine?

When we build automations for a business now, the outcome signal and the alert wiring ship with the workflow, as part of the same job. Not because it is sophisticated. Because we watched four of our own systems fail quietly in one week, and quiet is the expensive kind of broken.

Questions this post answers

How do I know if an automation is actually running?
Do not trust the exit code or the dashboard. Tie the automation to a number that should move when it works, a send count, a row count, a posted item, and check that number against what the system believed it would do. If the number and the belief disagree, or the number stops moving on a normal day, it is broken no matter how clean the logs look.
Why do automations fail silently instead of showing an error?
Because most error handling only covers the failures the author imagined. Catch-all exception handlers convert surprises into calm log lines, schedulers only know whether a process ran, not whether the work happened, and alert channels are often muted behind configuration nobody set. Silence is the default failure mode of unattended systems.
What is a proof-of-life signal for an automation?
A signal tied to the outcome the automation exists to produce, asserted every run and alarmed on absence: a delivery count that matches the queue, a heartbeat that fires only after real work completes, an alert when a normally daily system does nothing for days. Exit code zero is not proof of life; it only proves the process ran.

Want this working for your business?

We build the automation your team keeps meaning to build, then hand it over running. Book a call and we will map the first working slice.

Book a 20-minute call

Pick a time that works. Twenty minutes on video, no pitch. You leave knowing whether this is worth doing.