Imad's Universe
blog/the-scheduler-that-never-ran.md

The scheduler that never ran

19 August 2026 · 9 min read postmortem laravel

Two production apps had a scheduler configured, a cron entry firing every minute, and a log that said everything was fine. Neither had run a single scheduled task in months.

The symptom

Rank checks stopped updating. Not erroring — updating. The dashboard showed data from weeks ago and no exception anywhere. Every obvious thing checked out: the cron entry existed in hPanel, schedule:run executed on the minute, the log file grew.

What was actually happening

Laravel's Schedule::command() does not call your command. It builds a shell command and spawns a new PHP process to run it.

// this is a subprocess, not a method call
Schedule::command('rank:check')->hourly();

On shared hosting, proc_open, exec and shell_exec are disabled. The spawn silently fails. schedule:run exits zero, reports it dispatched the task, and nothing ever runs.

The fix

// runs in-process — no proc_open, no subprocess
Schedule::call(fn () => Artisan::call('rank:check'))->hourly();

That works, but it hides failures the same way. So the second half of the fix was a doctor command that proves the thing rather than asserting it.

The lesson worth keeping

A green log is not evidence. The scheduler reported success at every layer it could see, because from its own point of view it had succeeded — it handed the work to a process that never existed.

Anything whose only proof of life is its own log line needs a second, independent check.