Laravel Packages

Replay Missed Laravel Scheduled Tasks With Schedule Catchup

Published
Replay Missed Laravel Scheduled Tasks With Schedule Catchup image

Schedule Catchup is a Laravel package by Arowosaye Remilekun that runs scheduled tasks missed during downtime. If your server is offline at midnight, Laravel won't run the missed midnight command when it comes back. The package records scheduler activity in the database and checks for missed runs when the scheduler resumes.

Choose Which Tasks to Replay

Add replayOnCatchup() to a scheduled event in routes/console.php:

use Illuminate\Support\Facades\Schedule;

Schedule::command('reports:daily')
    ->dailyAt('00:00')
    ->withoutOverlapping()
    ->onOneServer()
    ->replayOnCatchup();

Only tagged events qualify for recovery. A report that must exist for each day is a reasonable candidate. A poller that already checks for outstanding work every five minutes may gain little from replaying its missed invocations.

For each tagged event, the package checks past cron slots and excludes those already recorded as successful or exhausted. It processes the remaining occurrences oldest first for that event. On its first run, with no previous checkpoint, it records a baseline rather than attempting to recover an unknown history.

Use the Original Scheduled Date

Running an old command again can produce the wrong result if it calculates dates from now(). A daily report replayed on Saturday might need Friday's data rather than today's.

The ResolvesScheduledTime trait provides scheduledFor(). During replay it returns the missed occurrence's timestamp; during a normal invocation it returns the current time. This command shows how to select the previous day's reporting period:

namespace App\Console\Commands;

use Cyberwizard\ScheduleCatchup\Traits\ResolvesScheduledTime;
use Illuminate\Console\Command;

class DailyReport extends Command
{
    use ResolvesScheduledTime;

    protected $signature = 'reports:daily';

    public function handle(): int
    {
        $reportDate = $this->scheduledFor()->subDay()->toDateString();

        $this->info("Reporting date: {$reportDate}");

        // Generate or update the report for $reportDate here.

        return self::SUCCESS;
    }
}

Existing calls to now() continue to use the current clock. You must update date-sensitive commands to use the helper where the intended period matters. The trait also exposes isCatchupReplay() if a command needs different behaviour during recovery.

Control Recovery and Inspect Pending Work

The default occurrence cap is 50 per event's cron walk. Version 1.1.0 stores a cursor when that cap is reached, allowing the remaining backlog to continue on later passes. Failed and deferred occurrences are stored separately for retry.

Failures stop being retried once their attempt count reaches max_attempts, which defaults to three. An overlap defers the occurrence without treating it as successful. In v1.1.0, background events run in the foreground during replay so the package can record their exit codes.

Inspect candidates before executing them:

php artisan schedule-catchup:run --dry-run --only=reports:daily

The --only option matches a substring of the event summary. Remove --dry-run to execute that selection. A filtered run does not advance the global checkpoint.

The package dispatches ScheduleCatchupCompleted after successful replays and ScheduleCatchupFailed for new failures or exhausted attempts. You can attach application listeners for notifications. Keep scheduler monitoring in place too: recovery requires the scheduler to start running again.

Installation and Replay Safety

Schedule Catchup requires PHP 8.2 or newer and works with Laravel 11, 12, and 13. Install it with Composer, then run the migrations:

composer require cyberwizard/schedule-catchup
php artisan migrate

The auto-discovered provider schedules catch-up and heartbeat commands every five minutes. It also schedules daily pruning of settled history and outage records, with a default retention of 90 days. For schedulers running on multiple servers, configure shared cache storage for the scheduler mutexes and package locks.

If a command sends an email and then crashes before the package records success, a retry could send the email again. Commands need to account for work already completed before a failure. For reports, updating a record keyed by reporting date is one way to avoid creating duplicates.

You can find the source and documentation in the Schedule Catchup GitHub repository.

Yannick Lyn Fatt photo

Staff Writer at Laravel News and Full stack web developer.

Sponsored

acquaintsoft logo
Acquaint Softtech

Hire Laravel developers with AI expertise at $20/hr. Get started in 48 hours.

Visit Acquaint Softtech

The latest

View all →
Search Polymorphic Relations and Filament Tables With Laravel Searchable image

Search Polymorphic Relations and Filament Tables With Laravel Searchable

Read article
Laravel Rest Api 3.0: Typed Fields, Metrics, and OpenAPI Docs image

Laravel Rest Api 3.0: Typed Fields, Metrics, and OpenAPI Docs

Read article
Real-Time Inertia Props With Laravel Reverb image

Real-Time Inertia Props With Laravel Reverb

Read article
PhpStorm 2026.3 Adds an Agent Workspace and PHP 8.6 Support image

PhpStorm 2026.3 Adds an Agent Workspace and PHP 8.6 Support

Read article
Insert Middleware Before or After Existing Laravel Middleware image

Insert Middleware Before or After Existing Laravel Middleware

Read article
Laravel Herd 2: Coding Agents, Workspaces, and Cloud Deploys image

Laravel Herd 2: Coding Agents, Workspaces, and Cloud Deploys

Read article