RightNow-AI/openfang · error

Failed to create Tokio runtime

Error message

Failed to create Tokio runtime

What it means

tokio::runtime::Runtime::new() fails when the runtime cannot build its I/O or time drivers — commonly inside a process that already has a Tokio runtime context, or when OS resources (epoll/eventfd, threads) are unavailable. create_backend() panics via expect, killing the MCP process when falling back to in-process kernel mode.

Source

Thrown at crates/openfang-cli/src/mcp.rs:155

fn create_backend(config: Option<std::path::PathBuf>) -> McpBackend {
    // Try daemon first
    if let Some(base_url) = super::find_daemon() {
        let client = reqwest::blocking::Client::builder()
            .timeout(std::time::Duration::from_secs(120))
            .build()
            .expect("Failed to build HTTP client");
        return McpBackend::Daemon { base_url, client };
    }

    // Fall back to in-process kernel
    let kernel = match OpenFangKernel::boot(config.as_deref()) {
        Ok(k) => k,
        Err(e) => {
            eprintln!("Failed to boot kernel for MCP: {e}");
            std::process::exit(1);
        }
    };
    let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
    McpBackend::InProcess {
        kernel: Box::new(kernel),
        rt,
    }
}

/// Read a Content-Length framed JSON-RPC message from the reader.
fn read_message(reader: &mut impl BufRead) -> io::Result<Option<Value>> {
    // Read headers until empty line
    let mut content_length: usize = 0;
    loop {
        let mut header = String::new();
        let bytes_read = reader.read_line(&mut header)?;
        if bytes_read == 0 {
            return Ok(None); // EOF
        }

        let trimmed = header.trim();

View on GitHub (pinned to acf2587e46)

Solutions

  1. Check ulimit -n / ulimit -u and raise fd/thread limits in constrained environments.
  2. Enable required tokio features (rt-multi-thread, net, time) in Cargo.toml.
  3. Build the runtime once at process start (lazy static / OnceLock) instead of inside create_backend to avoid repeated creation.
  4. Propagate the error and exit with a clear message rather than a panic string.
  5. Verify no tokio::test/#[tokio::main] wrapper already created a runtime around this code path.

Example fix

// before
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
// after
let rt = tokio::runtime::Runtime::new().unwrap_or_else(|e| {
    eprintln!("cannot start Tokio runtime: {e}");
    std::process::exit(1);
});
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify resource headroom before creating a runtime
fn runtime_env_ok() -> bool {
    // cheap proxy: can we spawn a thread?
    std::thread::Builder::new().stack_size(64 * 1024).spawn(|| {}).map(|h| h.join().is_ok()).unwrap_or(false)
}

Try / catch

// handle Result instead of expect
let rt = match tokio::runtime::Runtime::new() {
    Ok(rt) => rt,
    Err(e) => {
        eprintln!("Failed to create Tokio runtime: {e}");
        std::process::exit(1);
    }
};

Prevention

When it happens

Trigger: Runtime::new() called from create_backend() when not running inside an async context but the OS cannot provide reactor resources, or when called from code already inside another Tokio runtime (nested runtime creation is allowed for Runtime::new but worker/thread spawn failures raise errors), typically under thread/fd exhaustion.

Common situations: MCP server spawned by another async tooling host under low ulimit -u (thread limits) or fd limits; linking tokio features mismatched (no 'rt-multi-thread' or io driver features) causing driver init failure.

Related errors


AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02). Data as JSON: /api/errors/2edcbafb4b82ea74. Report an issue: GitHub.