leptos-rs/leptos · critical

At {caller}, tried to spawn a Future with Executor::spawn()

Error message

At {caller}, tried to spawn a Future with Executor::spawn() before a global executor was initialized.

What it means

In debug builds without the tracing feature, handle_uninitialized_spawn panics with this message when Executor::spawn is called before any global executor was initialized. The {caller} interpolation tells you which call site attempted the spawn. It is the diagnostic (as opposed to release no-op) path for the uninitialized-spawn condition.

Source

Thrown at any_spawner/src/lib.rs:474

/// Handles the case where `Executor::spawn` is called without an initialized executor.
#[cold] // Less likely path
#[inline(never)]
#[track_caller]
fn handle_uninitialized_spawn(_fut: PinnedFuture<()>) {
    let caller = std::panic::Location::caller();
    #[cfg(all(debug_assertions, feature = "tracing"))]
    {
        tracing::error!(
            target: "any_spawner",
            spawn_caller=%caller,
            "Executor::spawn called before a global executor was initialized. Task dropped."
        );
        // Drop the future implicitly after logging
        drop(_fut);
    }
    #[cfg(all(debug_assertions, not(feature = "tracing")))]
    {
        panic!(
            "At {caller}, tried to spawn a Future with Executor::spawn() \
             before a global executor was initialized."
        );
    }
    // In release builds (without tracing), call the specific no-op function.
    #[cfg(not(debug_assertions))]
    {
        no_op_spawn(_fut);
    }
}

/// Handles the case where `Executor::spawn_local` is called without an initialized executor.
#[cold] // Less likely path
#[inline(never)]
#[track_caller]
fn handle_uninitialized_spawn_local(_fut: PinnedLocalFuture<()>) {
    let caller = std::panic::Location::caller();
    #[cfg(all(debug_assertions, feature = "tracing"))]

View on GitHub (pinned to 32d20f6c9d)

Solutions

  1. Call any_spawner::spawn::set_executor(...) at the very start of the program/test before any spawn.
  2. In tests, add a shared initializer (e.g. once_cell Lazy) that sets a tokio or wasm executor.
  3. Check feature flags: enable tracing or the debug handler path you expect, or fix the init that a cfg gate disabled.
  4. Read the {caller} value in the panic message to find the offending spawn call and defer it until after executor init.

Example fix

// before
#[test]
fn spawns() {
    Executor::spawn(async {}); // panics: no executor
}
// after
#[test]
fn spawns() {
    any_spawner::spawn::set_executor(any_spawner::Executor::Tokio);
    Executor::spawn(async {});
}
Defensive patterns

Strategy: try-catch

Validate before calling

// The panic cannot be caught in-process via catch_unwind for normal use; validate init instead:
fn spawn_task<F: Future<Output = ()> + 'static>(fut: F) {
    crate::init::ensure_global_executor(); // must run before any spawn
    any_spawner::spawn(fut);
}

Try / catch

// only useful in tests/process boundaries
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    any_spawner::spawn(async {});
})).err().map(|p| eprintln!("spawn before executor init: {:?}", p));

Prevention

When it happens

Trigger: Any Executor::spawn call while no global executor is set (set_executor never called), compiled with debug_assertions and without the tracing feature.

Common situations: Unit tests that spawn tasks but never init an executor; early code running before app bootstrap; feature-flag changes that disabled the executor setup path.

Related errors


AI-assisted analysis of leptos-rs/leptos@32d20f6c9d (2026-09-01). Data as JSON: /api/errors/4894c36f18271079. Report an issue: GitHub.