clingen-data-model/laravel-schedule-ketchup
Laravel Schedule Ketchup adds catch-up support to Laravel's task scheduler, helping you detect and run scheduled jobs missed during downtime. Simple configuration, familiar scheduler integration, and reliable recovery for time-sensitive tasks.
This is the first tagged release (v1.0.0) of clingen-data-model/laravel-schedule-ketchup. The published changelog contains exactly one substantive item — a GitHub Actions workflow to run tests — so the tag is best read as "the code that already existed is now versioned and CI-verified" rather than a feature announcement. There is no prior API surface to compare against, so treat everything below as the starting contract and verify names against the README and src/ before you build on them.
Minimal path in:
composer require clingen-data-model/laravel-schedule-ketchup and pin it ("^1.0") — you are adopting a 1.0.0 with a single published release.php artisan vendor:publish --provider="...ScheduleKetchupServiceProvider"), then read the service provider's register()/boot() to see what is bound into the container and whether the scheduler is touched at boot.php artisan schedule:list before and after install to confirm nothing is silently re-registered.First use case: a scheduled task that must run on a fixed cadence — a nightly digest, a cache warm, a sync job — where the host was down, deploys overlapped, or a worker was stalled during the window. The "ketchup" pattern is to detect the missed window and run the task on the next opportunity instead of waiting a full interval. Start by wrapping one low-risk, idempotent command and confirm the behaviour end-to-end before widening it.
Wrap, don't rewrite. Keep your existing $schedule->command(...) definitions and add the catch-up behaviour around them. The migration cost of this package should be near zero; if you find yourself restructuring your schedule file, stop and re-read the README.
Register where your schedule already lives. In Laravel 11+ that is typically routes/console.php (or bootstrap/app.php via ->withSchedule()); on Laravel 10 and below it is app/Console/Kernel.php::schedule(). Keep the catch-up entries colocated with the normal entries so a reviewer can see both in one diff.
Only put idempotent work behind it. Catch-up semantics mean a task can execute outside its nominal window, and potentially more than once across a restart. Good candidates: report generation, search index refresh, cache priming, digest emails, reconciliation pulls from a remote API. Bad candidates: payment capture, one-shot notifications, anything with side effects that assume "exactly once per night."
Compose with the scheduler guards you already trust. Chain ->withoutOverlapping() and ->onOneServer() on any catch-up entry that runs on more than one node. withoutOverlapping() is backed by the cache lock, so confirm all nodes share one cache store — a file or array cache on a multi-node fleet gives you the lock in name only.
Cap the stampede. The failure mode of a catch-up scheduler is the opposite of the problem it solves: after a long outage, every missed window becomes due at once. If the package exposes a max-catch-up or lookback setting, set it explicitly rather than accepting the default (see Gotchas). If it does not, wrap your command body with your own "skip if we are more than N intervals behind" check.
Make catch-up runs observable. Emit a distinct log line or metric when a task runs because a window was missed, not because it was on time. Without that signal you cannot tell a healthy scheduler from one that is silently backfilling every night, and you will not notice a task that has effectively stopped running on schedule.
Test with a frozen clock. Carbon::setTestNow() to a timestamp past the expected window, then assert the task fires — and to a timestamp inside the window, then assert it does not. This is the only reliable way to exercise catch-up logic in a test suite, and it is what the newly added CI workflow is presumably doing.
@bpow) initial tag with one PR merged — budget for the API to move and pin your constraint.config/app.php). A DST transition creates one phantom miss and one phantom double-window per year. If the package stores last-run timestamps, check whether it stores them in UTC or app-local before you trust the arithmetic across a November rollback.withoutOverlapping() lock can suppress the next several runs. Know how to clear it (php artisan cache:clear on the relevant store, or the package's own reset if it provides one).php artisan schedule:list to confirm registration → php artisan schedule:run in a console to see it execute synchronously → check storage/logs/laravel.log filtered to the package namespace → then simulate a miss with Carbon::setTestNow() in php artisan tinker. Work that order; it isolates registration, execution, and trigger logic in three cheap steps.How can I help you explore Laravel packages today?