Troubleshooting

Tasks Not Executing

WP-Cron Is Not Running

Symptom: Tasks are registered but never execute.

Cause: WordPress's default cron relies on site visits. No traffic = no cron.

Solution: Set up a system-level cron job:

// wp-config.php
define('DISABLE_WP_CRON', true);
# System cron (every minute)
* * * * * curl -s https://yoursite.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1

ServiceProvider Not Registered

Symptom: $app->make(Schedule::class) throws an error.

Solution: Ensure the SchedulerServiceProvider is registered:

$app->register(new SchedulerServiceProvider());

Tasks Registered After Cron Hook

Symptom: Tasks are defined but getDueTasks() returns empty.

Solution: Register tasks before the cron hook fires. Use the ServiceProvider's boot() method or hook into an early action like plugins_loaded.

Task Executing Too Often or Not Often Enough

Minute-Level Precision

Symptom: Tasks execute approximately but not exactly at the specified time.

Cause: WP-Cron checks happen on page loads (or system cron ticks). There may be a 1-2 minute variance.

Solution: For exact timing, use a system cron job hitting wp-cron.php every minute.

Hourly Tasks Running Every Minute

Symptom: An hourly task runs every time the cron fires.

Cause: The isDue() check may not have the correct interval constraints.

Solution: Ensure you're chaining the frequency method:

// Good: Correct
$schedule->call($fn)->hourly()->name('my-task');

// Bad: Missing frequency — defaults to hourly but isDue() may always return true
$schedule->call($fn)->name('my-task');

Overlap Issues

Task Runs Twice Simultaneously

Symptom: A long-running task overlaps with itself.

Solution: Enable overlap prevention:

$schedule->call($fn)
    ->everyFiveMinutes()
    ->withoutOverlapping()
    ->name('long-task');

Task Never Runs After a Crash

Symptom: After a PHP fatal error during task execution, the task never runs again.

Cause: The lock was acquired but never released (task crashed before finally block or transient was set but process died).

Solution 1: Set a shorter lock expiration:

$schedule->call($fn)
    ->everyFiveMinutes()
    ->withoutOverlapping(30)  // Lock expires after 30 minutes
    ->name('crashable-task');

Solution 2: Manually clear the lock:

// Clear the stuck lock
delete_transient('scheduler_lock_' . md5('crashable-task'));

Lock Key Issues

Two Tasks Sharing a Lock

Symptom: When one task runs, another unrelated task is blocked.

Cause: Both tasks have the same name.

Solution: Use unique, descriptive names:

$schedule->call($fn1)->name('plugin-a-cleanup');
$schedule->call($fn2)->name('plugin-b-cleanup');

WP-CLI Commands Not Running

WP_CLI Not Defined

Symptom: Command tasks do nothing.

Cause: WP_CLI constant is only defined when running via WP-CLI, not during web requests or cron.

Solution: WP-CLI commands are designed for WP-CLI context. For cron tasks, use closures or jobs instead:

// Good: Use a closure for cron-executed tasks
$schedule->call(function () {
    wp_cache_flush();
})->hourly()->name('flush-cache');

// Warning: WP-CLI commands only work in WP-CLI context
$schedule->command('cache flush')->hourly()->name('cli-flush');

Job Dispatch Issues

Job Runs Synchronously

Symptom: Job tasks block the cron execution instead of being queued.

Cause: The wpzylos_dispatch_job filter isn't handled (queue package not installed).

Solution: Install and register the wpzylos-queue package, or accept synchronous execution.

Memory Issues

Cron Process Runs Out of Memory

Symptom: Allowed memory size exhausted during cron execution.

Solution 1: Increase memory limit for cron:

// wp-config.php
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');

Solution 2: Break large tasks into smaller chunks:

$schedule->call(function () {
    // Process in batches of 100
    $items = get_pending_items(100);
    foreach ($items as $item) {
        process_item($item);
    }
})->everyMinute()->name('batch-processor');

Debugging

Check Registered Tasks

$schedule = $app->make(Schedule::class);

foreach ($schedule->getTasks() as $task) {
    error_log(sprintf(
        'Task: %s | Type: %s | Interval: %ds | Due: %s',
        $task->getName(),
        $task->getType(),
        $task->getIntervalSeconds(),
        $task->isDue() ? 'YES' : 'NO'
    ));
}

Check Cron Events

# Using WP-CLI
wp cron event list

Check Active Locks

global $wpdb;
$locks = $wpdb->get_results(
    "SELECT option_name, option_value FROM {$wpdb->options}
     WHERE option_name LIKE '_transient_scheduler_lock_%'"
);
foreach ($locks as $lock) {
    error_log("Lock: {$lock->option_name} = {$lock->option_value}");
}

Listen for Failures

add_action('wpzylos_scheduler_task_failed', function ($task, $e) {
    error_log("[Scheduler FAIL] {$task->getName()}: {$e->getMessage()}");
    error_log($e->getTraceAsString());
}, 10, 2);