> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# The WooCommerce Cron That Never Ran Without Visitors
- URL: https://debugly.dev/woocommerce-cron-needs-visitors/
- Published: 2026-10-09T13:34:00.000Z
- Updated: 2026-10-10T13:47:27.000Z
- Description: The scheduled tasks were configured and due, and none ran, because the scheduler only fires on a page load, and the store was quiet, and the quiet period…
- Author: Rohit Bhadani
- Tags: WooCommerce, Ecommerce, DevOps

The store's scheduled tasks, the order follow ups, the inventory sync, the expired cart recovery, stopped running during the quiet season, and the quiet season was, of course, when the recovery emails and the sync mattered most, because the quiet is when the backlog forms. The tasks were not broken. They were due, and configured, and enabled, and none ran, because the scheduler that owns them only wakes when a visitor loads a page, and the quiet store had no visitors, and the scheduler, with no trigger, slept through every deadline.

This is the anatomy of a scheduler whose clock is the traffic, and of the specific failure mode that produces: tasks that run fine under load and vanish under quiet, which is the inverse of every capacity story on this site, and just as silent.

This was a WooCommerce store on WordPress with the default WP Cron, and the failure is the default behaviour, working as designed, for a design that assumes traffic is a clock, which is an assumption about the business, and the business did not agree to it.

## The mechanism: the page load is the trigger

The default scheduler is not a daemon. It is a hook: on each page load, the platform checks whether any task is due, and runs it inline, in the request, before serving the page. The design is elegant for a shared hosting world where a daemon is not available: the visitors are the cron, and a busy site has a precise clock. The design's contract is that traffic exists, and the contract's violation is a quiet site, where the check never runs, because the page never loads, and the due tasks accumulate, unseen, because the thing that would notice is the thing that never runs.

The cruelty is the correlation: the tasks that matter in the quiet, the win back email, the seasonal sync, the expiry notice, are precisely the tasks whose value is inverse to the traffic, and the scheduler's reliability is proportional to the traffic, so the reliability is lowest exactly when the value is highest, which is a scheduling system designed against its own purpose.

## Why the monitoring missed it

The tasks' absence is invisible to the usual monitoring, because the monitoring checks that the scheduler is enabled, which it is, and that the tasks are configured, which they are, and neither check asks whether the trigger fired, and the trigger is a page load, which is a business metric, not a system one, so the scheduler's health is coupled to the sales dashboard, and the sales dashboard is not wired to the operations alerts, which is the organisational half of the defect: the clock is a business quantity monitored by a different team.

This is the health check blindness from [outage caused by a successful health check](https://debugly.dev/outage-caused-by-successful-health-check/), same mechanism, different costume.

## What actually fixed it

**Replace the visitor clock with a real clock.** The platform supports disabling the inline trigger and running the scheduler from a genuine cron or a managed task, a system cron hitting the runner endpoint on a fixed interval, which decouples the clock from the traffic, and the decoupling is the fix, because the clock becomes a property of the infrastructure, monitored like infrastructure, instead of a property of the customers, monitored by nobody.

**Keep the inline trigger as a bonus, not the clock.** With the real cron in place, the page load trigger can remain as an extra tick for busy periods, which is harmless, but the schedule's correctness no longer depends on it, which is the division: the traffic may accelerate the clock, and must never be the clock.

**Alert on the last run age.** Each task's last execution is a metric, and the age of the last run against the task's period is the scheduler's health, and alerting on the age, when it exceeds a few periods, makes the quiet season failure a page instead of a discovery, which is the tripwire placed on the trigger's output rather than its configuration.

**Design the quiet season test.** The regression test for the scheduler is a period of zero traffic, simulated by simply not loading pages, with the real cron running, asserting the tasks fire on schedule, which is the test the default design fails and the decoupled design passes, and the test is the quiet season, made repeatable, which is the only way to test a failure whose production trigger is the absence of customers.

## What I would do differently

I would have asked, at setup, what the scheduler's clock is, and whether the business guarantees it, because every scheduler has a clock, and the clock is a dependency, and dependencies get named and monitored, and the visitor clock is a dependency on the customers, which is the one dependency the business cannot and should not guarantee, and the setup that names it would have replaced it on day one.

I would also have treated the quiet season as a load test, in reverse, because the quiet is the scheduler's peak load test, the condition under which it must work without help, and the condition under which it was never tested, because the testing happens during the busy season, when the inline trigger masks the defect, which is the sampling blindness from [the hydration mismatch that only happened with an ad blocker](https://debugly.dev/react-hydration-ad-blocker-mismatch/), in temporal form: the reproduction matrix excluded the quiet.

## The takeaway

A scheduler triggered by page loads has the traffic as its clock, and the traffic is a business quantity, unmonitored by operations and unguaranteed by anyone, so the tasks run under load and vanish under quiet, precisely when their value peaks. Replace the visitor clock with a real one, keep the inline tick as a bonus, alert on the last run age, and test the quiet season by design.

The tasks were never broken. They were waiting for a customer to arrive and wake the clock, and the quiet season, which is a business condition, became an operations incident. Get that property wrong and every layer above it lies to you politely.