Why Your Bot Missed a Day: Scheduling Reliability in the Cloud
The promise of automated DCA is a buy every day, no exceptions — that's the whole product. So it's an uncomfortable discovery that the most popular way to schedule a bot in the cloud makes no such promise. "Runs daily at 6:00" and "is triggered daily around 6:00, usually, when capacity allows" are very different sentences, and only one of them is what free schedulers actually offer.
The dirty secret of free cron
Most hobby bots — and plenty of serious ones — run on scheduled CI jobs, typically GitHub Actions with a schedule: trigger. It's free, it lives next to your code, and it works most of the time. But GitHub's own documentation is upfront that scheduled workflows are best-effort: they can be delayed during periods of high load, and delayed events can be dropped entirely. In practice, users report scheduled runs firing minutes to — during bad stretches — hours late, with high-traffic times (like the top of the hour) worst affected.
For a nightly cleanup script, who cares. For a bot doing daily DCA, drift has a real cost. A run that slips from 6:00 to 11:40 buys into a different market. A run that's dropped means a missed day — and a missed day silently breaks the contract that justified automating in the first place. If your buying tool's schedule is "whenever the free tier gets around to it," you've automated the clicking but not the discipline.
Failure modes, from annoying to expensive
Cloud scheduling fails in three distinct ways, and they need different defenses:
- Late fires. The run happens, just not when you said. Mostly harmless for a long-horizon stacker — the smoothing math barely notices a few hours — but it makes "did it run yet?" unanswerable and debugging miserable.
- Missed fires. The run never happens. Scheduler outage, dropped event, a repo quirk (GitHub, for instance, disables scheduled workflows on repositories with no recent activity). Days of gaps can accumulate before anyone notices.
- Double fires. The rarest and the scariest: retries, overlapping triggers, or a manual re-run alongside a late automatic one. Two executions of "spend today's budget" is exactly the kind of bug that turns $20 into $40 — every stacker's nightmare in miniature.
The external clock pattern
The fix for late and missed fires is architectural: separate the clock from the worker. Instead of trusting the CI platform's scheduler, run a tiny trigger on infrastructure that treats punctuality as a contract — cs2stack uses a Cloudflare Worker on a cron trigger, which fires on time with far better consistency — and have it do exactly one thing: call the API that dispatches the real job (a workflow_dispatch event, in GitHub terms). The heavy lifting still happens in the same environment as before; only the alarm clock moved.
This buys you three things. Punctuality, because the trigger is no longer competing with everyone else's 6:00 jobs on a best-effort queue. Observability, because the external clock can notice when a dispatch fails and retry or alert. And a second opinion: two independent systems now have to agree the run happened, which turns silent gaps into visible discrepancies.
Idempotency: the net under the trapeze
No scheduling setup gets you to zero anomalies — so the run itself must be safe to fire twice. That property is called idempotency: executing the same day's job a second time changes nothing, because the job first checks the ledger for what's already been bought today and buys only the remainder. If the answer is "everything," it does nothing and exits politely.
Once buys are idempotent, the whole reliability problem relaxes. A double fire costs nothing. A missed morning can be safely re-triggered by hand at lunch. The scheduler is demoted from a component you must trust to a component you merely prefer to work — the money-safety lives in the run logic, guarded further by daily budget caps and a hard ceiling, not in the hope that a free scheduler behaves. The mechanics deserve their own post: why good bots never buy twice.
Detecting the miss you didn't prevent
Defense in depth ends with detection: assume a day will eventually slip through anyway. The cheapest detector is one you already read — the daily buy report email doubles as a heartbeat, and "no email by breakfast" is a signal any human notices without tooling. Cheaper still is making gaps harmless by design: an idempotent bot with an append-only ledger can be re-run for a missed day with one click, and the record shows exactly what happened and when. (One thing it deliberately should not do is retroactively double-buy to compensate — for the same reasons new items start at zero.)
Does one missed day even matter? Financially, almost never — over a multi-year stack, a single skipped $20 disappears into noise, just as cadence comparisons show daily-versus-weekly barely moves outcomes. But reliability isn't really about the $20. A bot that misses days erodes the one thing that makes automation worth having: the ability to stop thinking about it. You either trust the machine to run every morning or you're back to checking on it — which was the job you were trying to quit. Punctual clock, idempotent runs, heartbeat email. Then go back to ignoring it, which was the point.