dbt-labs/dbt-core · critical

failed to initialize 'single-worker' tokio runtime

Error message

failed to initialize 'single-worker' tokio runtime

What it means

`run_cli_with_code` builds a single-worker tokio runtime for the CLI; if `tokio::runtime::Builder::build()` returns Err it panics with this message. Build fails only when the runtime's worker thread cannot be created — typically thread spawn failure from memory/pid limits or a pathological configured stack size.

Solutions

  1. Check ulimit -u and raise it, or reduce concurrent processes
  2. Verify container memory/pids cgroup limits and increase them
  3. Remove or reduce custom stack-size env overrides
  4. Retry; if persistent, run with RUST_BACKTRACE=1 and report

Example fix

null
Defensive patterns

Strategy: try-catch

Try / catch

let rt = tokio::runtime::Builder::new_current_thread()
    .enable_all().build()
    .or_else(|_| std::panic::catch_unwind(|| build_default_runtime()))
    .expect("no usable tokio runtime");

Prevention

When it happens

Trigger: Starting dbt (via `run_cli`) with single-thread mode selected and the OS refusing to create the worker thread: ulimit -u reached, cgroup thread cap, OOM, or `FS_DEFAULT_STACK_SIZE` so large the stack cannot be mmap'd.

Common situations: Containers with tight memory or pids limits; machines under heavy load; mis-sized stack-size env overrides; running many concurrent dbt processes.

Related errors


AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/02791898fdd7c538. Report an issue: GitHub.

Appendix: source

Thrown at crates/dbt-main/src/main_impl.rs:167

        .build();

    // Setup tokio runtime and set stack-size to 8MB
    // DO NOT USE Rayon, it is not compatible with Tokio

    // Only `--no-parallel` pins the tokio runtime to a single worker.
    // `--threads` is exclusively the adapter connection-backpressure knob
    // and does not affect the runtime.
    let tokio_rt = if arg.no_parallel {
        let dbt_rt_handle = dbt_rt.handle().clone();
        tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .thread_stack_size(FS_DEFAULT_STACK_SIZE)
            .worker_threads(1)
            .max_blocking_threads(1)
            .on_thread_start(move || on_tokio_thread_start(&dbt_rt_handle))
            .on_thread_stop(on_tokio_thread_stop)
            .build()
            .expect("failed to initialize 'single-worker' tokio runtime")
    } else {
        let dbt_rt_handle = dbt_rt.handle().clone();
        // Multi-threaded runtime: use default (max parallelism)
        tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .max_blocking_threads(FS_DEFAULT_MAX_BLOCKING_THREADS)
            .thread_stack_size(FS_DEFAULT_STACK_SIZE)
            .on_thread_start(move || on_tokio_thread_start(&dbt_rt_handle))
            .on_thread_stop(on_tokio_thread_stop)
            .build()
            .expect("failed to initialize default multi-threaded tokio runtime")
    };

    // If execution panics, exit with a status 2 (but not if RUST_BACKTRACE is
    // set to 1, in which case we want to see the backtrace):
    if arg.exit_process_on_panic && std::env::var("RUST_BACKTRACE").unwrap_or_default() != "1" {
        std::panic::set_hook(Box::new(|info| {
            eprintln!("{} {}", RED.apply_to(format!("{PANIC}:")), info);

View on GitHub (pinned to 0267ce9170)