BloopAI/vibe-kanban · critical

failed to build global Tokio runtime

Error message

failed to build global Tokio runtime

What it means

rt() builds a process-global Tokio multi-thread runtime inside a OnceLock and panics with expect("failed to build global Tokio runtime") if tokio::runtime::Builder::build() fails. build() essentially only fails when the runtime configuration is invalid (e.g. worker counts below the minimum or a bug in runtime options), or in constrained environments where OS resources (threads) cannot be allocated. Because the result is cached in a OnceLock, any panic here poisons the first call to block_on and every subsequent sync-to-async bridge call.

Source

Thrown at crates/utils/src/tokio.rs:11

use std::{future::Future, sync::OnceLock};

use tokio::runtime::{Builder, Handle, Runtime, RuntimeFlavor};

fn rt() -> &'static Runtime {
    static RT: OnceLock<Runtime> = OnceLock::new();
    RT.get_or_init(|| {
        Builder::new_multi_thread()
            .enable_all()
            .build()
            .expect("failed to build global Tokio runtime")
    })
}

/// Run an async future from sync code safely.
/// If already inside a Tokio runtime, it will use that runtime.
pub fn block_on<F, T>(fut: F) -> T
where
    F: Future<Output = T> + Send,
    T: Send,
{
    match Handle::try_current() {
        // Already inside a Tokio runtime
        Ok(h) => match h.runtime_flavor() {
            // Use block_in_place so other tasks keep running.
            RuntimeFlavor::MultiThread => tokio::task::block_in_place(|| rt().block_on(fut)),
            // Spawn a new thread to avoid freezing a single-thread runtime.
            RuntimeFlavor::CurrentThread | _ => std::thread::scope(|s| {
                s.spawn(|| rt().block_on(fut))

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Check ulimit -u / RLIMIT_NPROC and raise the thread/process limit if it is exhausted.
  2. Ensure the process isn't memory-starved; free memory or increase container limits.
  3. If embedding in a runtime-restricted host, provide your own runtime and call runtime.block_on directly instead of going through block_on().
  4. Refactor rt() to return Result<&'static Runtime, tokio::io::Error> (or use get_or_try_init) so the error is reported instead of panicking.

Example fix

// before
RT.get_or_init(|| {
    Builder::new_multi_thread()
        .enable_all()
        .build()
        .expect("failed to build global Tokio runtime")
})
// after
RT.get_or_try_init(|| {
    Builder::new_multi_thread()
        .enable_all()
        .build()
}).map_err(|e| anyhow::anyhow!("failed to build global Tokio runtime: {e}"))?
Defensive patterns

Strategy: try-catch

Validate before calling

// sanity-check the process can spawn threads before bridging into async
use std::thread;
match thread::Builder::new().name("probe").spawn(|| {}) {
    Ok(h) => { let _ = h.join(); }
    Err(e) => eprintln!("cannot create threads (check ulimit -u / RLIMIT_NPROC): {e}"),
}

Try / catch

let out = std::panic::catch_unwind(|| vibe_utils::block_on(future))
    .map_err(|_| anyhow::anyhow!("global tokio runtime failed to build (check thread limits)"))?;

Prevention

When it happens

Trigger: Calling block_on() (the only caller of rt()) in a process where Builder::new_multi_thread().enable_all().build() returns Err — practically: extreme thread-creation failures (rlimit/ulimit on threads/processes exhausted), or exotic platforms where multi-thread runtime construction is unsupported.

Common situations: Embedding the library in a process with nproc/thread rlimits set to 1; sandboxed environments (some CI sandboxes, WASM-adjacent or seccomp-restricted setups) that forbid thread creation; memory exhaustion during runtime spawn.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/554234ed9de1ae43. Report an issue: GitHub.