All skills
asyrafhussin avatar

/laravel-queues

@2631530

Laravel 13 queue and job patterns — driver choice, job design (idempotency, ShouldQueue, model serialisation), retry and failure handling, worker scaling, Bus batching and chaining, Horizon when warranted, queue testing. Use when designing async jobs, scheduling background work, configuring Horizon, debugging stuck jobs, or auditing queue health. Triggers on "Laravel queue", "Laravel job", "background job", "Horizon setup", "failed jobs", "Bus batch", "queue worker tuning".

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/laravel-queues

This session only. Nothing lands on disk.

rulesbus-batch-vs-chain.md

≈1.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Bus::batch vs Bus::chain — Pick the Right One

Impact: HIGH (Wrong choice means parallel work runs serial (slow) or sequential work runs in parallel (race conditions))

When dispatching multiple related jobs, Laravel offers two APIs that look similar but behave very differently:

  • Bus::chain([...]) — strictly sequential. Each job runs only after the previous one succeeds. If any job fails, the rest are abandoned.
  • Bus::batch([...]) — parallel by default. All jobs queue immediately; workers pick them up concurrently. Tracks aggregate progress; offers then, catch, finally callbacks.

Picking the wrong one creates real bugs.

Decision tree

Scenario Use
Step B depends on step A's side effects Bus::chain (sequential, abort on failure)
Process 1000 items, order doesn't matter Bus::batch (parallel)
Want a "done when all are done" callback Bus::batch
Want a progress bar / status page Bus::batch
Workflow with branching ("do A, then B and C in parallel, then D") Bus::chain containing Bus::batch
One job's failure should NOT stop the others Bus::batch->allowFailures()
One job's failure SHOULD stop everything after Bus::chain

Bus::chain — strict sequence

// ✅ Each step depends on the previous
use App\Jobs\OptimizePodcast;
use App\Jobs\ProcessPodcast;
use App\Jobs\ReleasePodcast;
use Illuminate\Support\Facades\Bus;

Bus::chain([
    new ProcessPodcast(podcastId: 42),     // step 1
    new OptimizePodcast(podcastId: 42),    // step 2 — runs only if step 1 succeeded
    new ReleasePodcast(podcastId: 42),     // step 3 — runs only if step 2 succeeded
])->dispatch();

If ProcessPodcast fails (all tries exhausted), OptimizePodcast and ReleasePodcast are never dispatched. The chain stops dead.

You can add catch to handle chain failure:

Bus::chain([
    new ProcessPodcast(42),
    new OptimizePodcast(42),
    new ReleasePodcast(42),
])->catch(function (Throwable $e) {
    // Chain failed; clean up partial state
    Podcast::find(42)?->markProcessingFailed($e->getMessage());
})->dispatch();

Bus::batch — parallel + aggregate tracking

// ✅ Process 500 CSV rows in parallel, with progress callbacks
use App\Jobs\ImportCsvRow;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
use Throwable;

$batch = Bus::batch(
    Csv::rows($file)->map(fn ($row) => new ImportCsvRow($row))
)->name('CSV import — ' . $file->name)
 ->before(function (Batch $batch) {
    // Batch has been created, jobs not yet added
 })
 ->progress(function (Batch $batch) {
    // Called after each job completes
    broadcast(new BatchProgressUpdated($batch));
 })
 ->then(function (Batch $batch) {
    // All jobs succeeded
    Notification::send($user, new CsvImportComplete($batch->id));
 })
 ->catch(function (Batch $batch, Throwable $e) {
    // First job failure
    Log::error('CSV batch failed', ['batch_id' => $batch->id, 'error' => $e->getMessage()]);
 })
 ->finally(function (Batch $batch) {
    // Always called — success or failure
    Storage::delete($file->path);
 })
 ->onQueue('imports')
 ->dispatch();

return ['batch_id' => $batch->id];   // give the user a way to poll progress

By default, a single failure cancels the rest of the batch. Use allowFailures() to keep going (see next rule).

Mixing them — chain that contains batches

// ✅ Step 1 (single), then step 2 (parallel batch), then step 3 (single)
Bus::chain([
    new FlushPodcastCache(42),
    Bus::batch([
        new ReleasePodcast(42, region: 'us-east'),
        new ReleasePodcast(42, region: 'eu-west'),
        new ReleasePodcast(42, region: 'ap-south'),
    ]),
    new NotifyPodcastReleased(42),
])->dispatch();

This runs:

  1. FlushPodcastCache first
  2. Then three ReleasePodcast jobs in parallel (batch waits for all to finish)
  3. Then NotifyPodcastReleased after the batch completes

Incorrect

// ❌ Using chain when jobs are independent — slow

Bus::chain([
    new SendOrderConfirmation(1),   // independent
    new SendOrderConfirmation(2),   // independent
    new SendOrderConfirmation(3),   // independent
    // ... 100 more
])->dispatch();
// Result: 100 emails sent one at a time. The 100th waits ~5 minutes.
// Should have been Bus::batch — parallel.
// ❌ Using batch when there's a dependency — race condition

Bus::batch([
    new CreateInvoice($orderId),   // creates invoice row
    new EmailInvoiceLink($orderId), // needs the invoice row to exist
])->dispatch();
// Race: EmailInvoiceLink may run before CreateInvoice finishes. Email links to a row
// that doesn't exist yet. Should have been Bus::chain.

Detection

# Find Bus::chain with many jobs — likely should be Bus::batch
grep -rEnA20 'Bus::chain\(' --include='*.php' app/ \
  | awk '/Bus::chain/,/\)\->/{print}' | grep -c 'new '

# Find Bus::batch where dependency between jobs is implied by name
# (manual review — look at the job names in batch arrays)
grep -rEnA5 'Bus::batch\(' --include='*.php' app/ | head -30

Reference: Laravel 13 — Queues: Job Chaining · Laravel 13 — Queues: Job Batching

Source: SKILL.md on GitHub

No alerts16d3 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The analyzed skill contains high-quality reference guidelines and checklists for configuring and auditing Laravel 13 queue structures and background jobs. No security risks, malicious patterns, or vulnerabilities were identified.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

Signed by skilld at 2631530. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 5 months ago
metadata
{
  "author": "agent-skills",
  "version": "1.0.0"
}

README badge

README badge for asyrafhussin/agent-skills/laravel-queues