laravel/framework · error · RuntimeException

To enable support for closure jobs, please install the illum

Error message

To enable support for closure jobs, please install the illuminate/queue package.

What it means

Queueable::serializeJob() wraps a Closure into CallQueuedClosure before serializing it into a job chain. If the Illuminate\Queue\CallQueuedClosure class does not exist (the illuminate/queue package is not installed), it throws a RuntimeException telling the user to install it. Closure chaining requires the queue subsystem.

Source

Thrown at src/Illuminate/Bus/Queueable.php:321

            $this->chained = array_merge($this->chained, [$this->serializeJob($job)]);
        }

        return $this;
    }

    /**
     * Serialize a job for queuing.
     *
     * @param  mixed  $job
     * @return string
     *
     * @throws \RuntimeException
     */
    protected function serializeJob($job)
    {
        if ($job instanceof Closure) {
            if (! class_exists(CallQueuedClosure::class)) {
                throw new RuntimeException(
                    'To enable support for closure jobs, please install the illuminate/queue package.'
                );
            }

            $job = CallQueuedClosure::create($job);
        }

        return serialize($job);
    }

    /**
     * Dispatch the next job on the chain.
     *
     * @return void
     */
    public function dispatchNextJobInChain()
    {
        if (is_array($this->chained) && ! empty($this->chained)) {

View on GitHub (pinned to bd6b5437e6)

Solutions

  1. Run 'composer require illuminate/queue' to provide CallQueuedClosure and the queue runtime.
  2. Alternatively, replace the closure with a concrete job class so no CallQueuedClosure wrapping is needed.
  3. If closures must be used, also install laravel/serializable-closure (a dependency used by CallQueuedClosure).

Example fix

// before
$job->chain([
    function () { logger('done'); },
]);

// after (option A: install package)
// composer require illuminate/queue

// after (option B: use a class)
class LogDoneJob implements ShouldQueue {
    use Queueable;
    public function handle() { logger('done'); }
}
$job->chain([new LogDoneJob()]);
Defensive patterns

Strategy: validation

Validate before calling

if (! class_exists(\Illuminate\Queue\CallQueuedClosure::class)) {
    throw new \RuntimeException('Install illuminate/queue to chain closure jobs.');
}
$job->chain([$closureJob]);

Type guard

function closureJobsAreSupported(): bool
{
    return class_exists(\Illuminate\Queue\CallQueuedClosure::class)
        && class_exists(\Laravel\SerializableClosure\SerializableClosure::class);
}

Try / catch

try {
    $job->chain([fn () => doWork()]);
} catch (\RuntimeException $e) {
    if (str_contains($e->getMessage(), 'illuminate/queue')) {
        // run composer require illuminate/queue then retry
    }
    throw $e;
}

Prevention

When it happens

Trigger: Calling $job->chain([function () { ... }]) or appendToChain(function () {...}) in an installation that has illuminate/bus but not illuminate/queue. Serializing a closure-based job via the Queueable trait without the queue package present.

Common situations: Using illuminate/bus or illuminate/pipeline standalone packages and trying to chain closures. A partial composer require that pulled bus but not queue. Refactoring a monolith into a slim worker that omits illuminate/queue.

Related errors


AI-assisted analysis of laravel/framework@bd6b5437e6 (2026-08-06). Data as JSON: /data/errors/bdd41637b27643bb.json. Report an issue: GitHub.