Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Queue Batch Retry Laravel Package

kirschbaum-development/laravel-queue-batch-retry

Retry failed jobs in Laravel queue batches with a simple artisan command. This package re-dispatches failed batch jobs while preserving batch context, so you can recover from failures without manually tracking or re-queuing individual jobs.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites — this is not a general-purpose tool. The package acts on Laravel's native job_batches and failed_jobs tables. If the codebase doesn't use Bus::batch(), there is nothing to retry and the evaluation ends. Confirm both tables are present and retained long enough to be useful — pruning job_batches aggressively defeats the whole point.

Install:

composer require kirschbaum-development/laravel-queue-batch-retry:^1.2 --dev

Pin at ^1.2, not ^1.0. --exclude-filter landed in 1.2.2; a stale lockfile or a dev box on 1.2.1 will silently lack the flag you built your runbook around. There are no migrations, no publishable config, and no service provider wiring beyond auto-discovery — installation is genuinely a one-liner and should not be the bulk of the work.

Where to look first — in this order:

  1. php artisan list | grep -i batch to confirm the command signature. Do not assume — the package's command may share a name or namespace with Laravel's own queue:retry-batch, and the documentation does not spell out the literal signature. Check for a collision on your Laravel version before scripting against it.
  2. Read the command class in vendor/kirschbaum-development/ and the underlying retry helper. The single most important unknown is what --filter and --exclude-filter match against (job class FQCN? queue? connection? namespace prefix?) and whether the match is exact, prefix, substring, or regex. The release notes for 1.2.2 only say the exclusion flag was added.
  3. Identify the programmatic helper class if you intend to call retry from application code rather than the CLI.

First use case — the standard recovery loop: A bulk import batch of 40,000 jobs finishes with 12 transient failures. Query the batch and its failures before touching anything:

$batch = Bus::findBatch($batchId);
dump($batch->failedJobIds, $batch->pendingJobs, $batch->processedJobs());

$failed = DB::table('failed_jobs')
    ->whereIn('uuid', $batch->failedJobIds)
    ->get(['uuid', 'queue', 'payload']);

Read the payloads to see which job classes actually failed, decide the filter from that, then invoke the retry. Run it against a staging database restored from a production snapshot before the first production use — treat the first invocation as characterization, not recovery.

Implementation Patterns

Filter-first, dispatch-second. The command is a mutating write against live queue state. The working habit is: inspect → count → dispatch → verify. Build the intended selection as a query first, confirm the count matches what you expect (3 failures, not 3,000), and only then run the command. Because there is no documented --dry-run, wrap the whole thing in a small internal Artisan command or ops script if you need confirmation gates — that wrapper is where you enforce "the count is what I expect" and "print the selected UUIDs."

Selective recovery with --filter plus --exclude-filter. The 1.2.2 addition is what turns a binary "replay everything or hand-pick UUIDs" into a scoped operation. The idiom to aim for is: include the healthy area, exclude the known-bad dependency. Conceptually:

  • Include everything in the App\Jobs\Billing\ namespace, exclude the StripeChargeJob you know is broken upstream — make forward progress on the rest of the batch instead of stalling the whole recovery on one integration.
  • Use --filter alone for the common case where exactly one job class is failing and you want to re-run only that class.

Both flags combined — verify precedence before you rely on it. The sources do not document whether exclusion wins over inclusion on overlap. Test the overlap case explicitly in staging (a job that matches both) and record the answer in the runbook. Never deploy a filter that assumes a precedence you haven't confirmed.

Callbacks stay attached — this is the entire reason to use the package. Batch then/catch/finally callbacks still fire because the retry re-enters the existing batch rather than building a fresh one. That means progress counters, completion notifications, and cleanup handlers behave correctly. If you'd otherwise be re-dispatching failures manually via Bus::batch([...])->dispatch(), you'd break that bookkeeping — do not hand-roll it as a shortcut.

Idempotency is your job, not the package's. A retried job re-executes from scratch, against whatever external state the first attempt already wrote. Before you retry anything, the job body needs one of:

  • An external-idempotency-key guard (e.g. a processed_tokens row checked and set in a transaction, or an API's native idempotency header).
  • ShouldBeUnique / WithoutOverlapping if the failure mode is concurrency rather than a transient dependency.
  • A "did this row already land?" existence check before the downstream write.

Write this into the runbook as a precondition, not a footnote. Retrying a non-idempotent job that partially succeeded is how you get duplicate customer-visible side effects, which is a worse incident than the original failure.

Testing the retry path. In a feature test: dispatch a batch where a documented subset of jobs throw, run the retry helper programmatically (the package advertises programmatic as well as Artisan recovery), and assert (1) only the failed subset was re-dispatched, (2) the batch reached a terminal state, (3) then/finally fired. This is also the cheapest way to nail down filter semantics — assertions on which jobs were re-dispatched are the semantics documentation.

Runbook discipline. Name a single owner for retry operations in the codebase's ops docs and decide explicitly whether this package or Horizon owns retries. Two interfaces to the same failed_jobs mutation is how you get a double-dispatch during a stressed incident.

Gotchas and Bugs

Filter semantics are undocumented — this is the primary risk, not a detail. Exact vs. prefix vs. substring vs. regex, and what attribute is matched, is not in the release notes. An over-broad filter floods workers and duplicates side effects; an over-broad exclusion silently skips the one job you needed. Read the source, test on staging, write worked examples into the runbook. Any "unknown" on filter behavior means pilot-only, not production.

Empty or fully-excluded selection can strand the batch. If --exclude-filter removes every remaining failure, or the selection is empty, verify what happens to the batch's terminal state — specifically whether catch/finally still fire. A batch that can never complete leaves bookkeeping (progress trackers, completion notifications, cleanup) permanently wrong. Test the "exclude everything" case deliberately.

Concurrent or repeated invocation is not documented as guarded. Nothing in the sources indicates a lock. Two operators running the retry during an incident, or a script retried after a timeout, can double-dispatch. Enforce a named owner and, if you script it, put a lock or a "was a retry already run for this batch" check in front. Confirm whether a second invocation re-dispatches jobs already retried.

Command name collision with core queue:retry-batch. Laravel ships a whole-batch retry command and Horizon ships another retry path. Confirm which command your operators actually invoke and that the package's registration doesn't shadow or get shadowed. If you don't need filtering, prefer core — smaller risk surface — and reserve this package for the selective-recovery case that motivated adoption.

Empty failed_jobs / pruned batches. If jobs are pruned from failed_jobs but still referenced by batch->failedJobIds (or vice versa), you can get partial or silently-empty selections. Check your retention policy for both tables before an incident, not during one.

Version and maintenance lag. 51 stars, one maintainer plus a drive-by contributor, last release 2024-06-11. There's no published Laravel/PHP support matrix in the sources. Pin ^1.2, add it as a dev/ops dependency off the deploy path, and re-verify on Laravel major upgrades. Keep a short fallback note — a 20-line Artisan command over job_batches/failed_jobs is the exit ramp if the package falls behind.

Scope boundaries — don't stretch it. This is operational tooling, not a workflow engine. No dependency graphs, sagas, compensation, audit trail, approval gates, or RBAC. If the roadmap calls for any of those, the answer is Temporal/Laravel Workflow, not a filter flag bolted onto this command — say so early rather than retrofitting.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log