Engineering

The nightly job that quietly stopped running

Writing a nightly job is the easy part. Make lets you pick a time down to the minute on any paid plan, and the schedule panel takes about ten seconds to fill in. The hard part arrives months later, when the job stops and the first person to notice is a customer asking why their report is three days old. Platform schedulers are good at running things. They are much weaker at telling you when they stopped.

The retry ladder ends by switching the job off

When a module throws a ConnectionError or a ModuleTimeoutError, Make reruns the scenario on an exponential backoff. With incomplete executions turned off, the waits go 1 minute, 2, 5, 10, then an hour, 3 hours, 12 hours, 24 hours. Add those up and the eighth and final attempt lands about 40 hours after the first failure. If that one fails too, Make disables scheduling for the scenario.

Read that sequence again with a calendar next to it. An API that goes down Friday evening burns through every retry before anyone opens a laptop on Monday. The scenario is still sitting in the list, still looks like yours, and is no longer scheduled. Nothing about the canvas looks different.

This is sensible behaviour for a shared platform. Make cannot keep hammering a dead endpoint forever on your behalf. The problem is that the decision to stop is made by the platform, on the platform's terms, and the only lever you have is turning it back on after you find out.

You cannot grep a log you cannot query

Make keeps detailed execution logs for 7 days on the Free plan and 30 days on Core, Pro and Teams. Full-text search across those logs is a Pro feature, which starts at $21 a month for 10,000 credits. Below that you are clicking through execution history by hand, one run at a time, looking for the first bad one.

A job that fails loudly is not the dangerous case. The dangerous case is the one that ran, returned 200, and wrote nothing, because the upstream API changed a field name and your mapping silently resolved to empty. No error, no retry, no backoff. Just a quiet gap in the data that gets harder to backfill every week it survives.

On the Free plan the constraints stack up in a way worth knowing before you build on them: 2 active scenarios at a time, a 15 minute floor between runs, and a 5 minute cap on how long a single execution can take. None of that is hidden. It is on the pricing page. It is just easy to design past it and notice later.

What the same job looks like when the code is yours

A Cloudflare Workers cron is a crons array in the config file and a scheduled() handler in your repo. Five lines of JSON and a function. You get 5 cron triggers per account on the free plan and 250 on paid, with 30 seconds of CPU time per invocation under an hour interval and 15 minutes of wall time per run.

The difference is not the scheduling. It is everything around it. The handler is a function, so you can write a test for it. It lives in git, so a bad change shows up in review. You can fire it locally with curl against /cdn-cgi/local/scheduled and even pass a fake cron expression and timestamp to see how it behaves at month end. Cloudflare keeps the last 100 invocations under Cron Events, and Workers Logs holds more with a query interface, but the useful part is that the alerting is yours to write. If the job is supposed to insert 40 rows and inserts zero, you decide that counts as a failure, because you wrote the line that says so.

When we moved Stay World Class off Webflow and Xano to Next.js, NestJS and Supabase, the data move ran as a 4-stage ETL that mapped legacy IDs to PostgreSQL UUIDs. The staging mattered more than the speed: every stage could be inspected and rerun against a known input. Lighthouse went from 55.91 to 91 out of 100 and LCP got 77% faster, which is the number people quote, but the quieter win was that the recurring jobs stopped being something you had to go and look at.

When the platform scheduler is the right answer

If the job is a convenience rather than a dependency, keep it where it is. A weekly Slack digest or a nightly tidy-up of a spreadsheet does not need retry semantics, and nobody is harmed if it skips a Tuesday. Make's backoff will handle the transient failures better than most first attempts at a hand-rolled retry loop.

It also stays the right answer when the job is mostly glue between two SaaS tools that already have connectors. Rebuilding an OAuth flow with Google Sheets in order to own a cron you run once a day is a poor trade. The connector library is real value and it does not disappear because you can write code.

The line sits where the job becomes something a customer or an accountant depends on. If a missing run means wrong invoices, a stale availability calendar, or a compliance report that never left the building, you need to know within minutes rather than at the end of a 40 hour retry ladder. That alert is the thing you cannot buy from the schedule panel, and it is usually what tips a job out of the platform and into code.

Sources

Is your platform outgrowing its stack?

Book a call and we'll walk through how we'd approach your platform, with an estimate grounded in a working prototype.