laravel/framework · error · RuntimeException

Scheduled closures can not be run in the background.

Error message

Scheduled closures can not be run in the background.

What it means

Thrown by CallbackEvent::runInBackground() (src/Illuminate/Console/Scheduling/CallbackEvent.php:104) unconditionally. Closures/anonymous callbacks cannot be serialized to a background process command string, so Laravel refuses to background them.

Source

Thrown at src/Illuminate/Console/Scheduling/CallbackEvent.php:104

     * Determine if the event should skip because another process is overlapping.
     *
     * @return bool
     */
    public function shouldSkipDueToOverlapping()
    {
        return $this->description && parent::shouldSkipDueToOverlapping();
    }

    /**
     * Indicate that the callback should run in the background.
     *
     * @return void
     *
     * @throws \RuntimeException
     */
    public function runInBackground()
    {
        throw new RuntimeException('Scheduled closures can not be run in the background.');
    }

    /**
     * Run the callback.
     *
     * @param  \Illuminate\Contracts\Container\Container  $container
     * @return int
     */
    protected function execute($container)
    {
        try {
            $this->result = is_object($this->callback)
                ? $container->call([$this->callback, '__invoke'], $this->parameters)
                : $container->call($this->callback, $this->parameters);

            return $this->result === false ? 1 : 0;
        } catch (Throwable $e) {
            $this->exception = $e;

View on GitHub (pinned to bd6b5437e6)

Solutions

  1. Remove runInBackground() from closure-based events and let them run in the foreground.
  2. Move the logic into a real Artisan command and schedule it with Schedule::command('name') (a regular Event supports background).
  3. Dispatch the work to a queue job instead and schedule the job (still foreground at the schedule layer).
  4. If set at group level, split closures out of the grouped schedule.

Example fix

// before
$schedule->call(fn () => heavyWork())
    ->daily()
    ->runInBackground();

// after
$schedule->call(fn () => heavyWork())->daily();
// or convert to a command:
$schedule->command('reports:heavy')->daily()->runInBackground();
Defensive patterns

Strategy: validation

Validate before calling

if ($event instanceof \Illuminate\Console\Scheduling\CallbackEvent) {
    // do NOT chain runInBackground(); run in foreground or convert to a command
}

Type guard

function isClosureEvent($event): bool
{
    return $event instanceof \Illuminate\Console\Scheduling\CallbackEvent;
}

Try / catch

try {
    $event->runInBackground();
} catch (\RuntimeException $e) {
    // keep foreground execution
}

Prevention

When it happens

Trigger: Chaining ->runInBackground() on a Schedule::call(...) or Schedule::job(...) event (both produce CallbackEvent). Also when a scheduled closure inherits background behavior (e.g. inside a Schedule::group() that sets runInBackground) — the merge triggers the method on the closure event.

Common situations: Copy-pasting a command-style schedule (which supports background) onto a closure-based schedule, or applying runInBackground at the group level without realizing closures are included.

Related errors


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