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
- Check ulimit -n / ulimit -u and raise fd/thread limits in constrained environments.
- Enable required tokio features (rt-multi-thread, net, time) in Cargo.toml.
- Build the runtime once at process start (lazy static / OnceLock) instead of inside create_backend to avoid repeated creation.
- Propagate the error and exit with a clear message rather than a panic string.
- 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
- Create the runtime once per process (OnceLock), not per backend construction.
- Check ulimit -n/-u in constrained MCP host environments.
- Enable tokio rt-multi-thread/net/time features so drivers exist.
- Avoid creating blocking runtimes inside an existing async runtime context.
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
- Failed to listen for SIGTERM
- Failed to build HTTP client
- Failed to create tokio runtime for embedded server
- MCP message too large: {content_length} bytes (max {MAX_MCP_
- Failed to listen for SIGINT
AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02).
Data as JSON: /api/errors/2edcbafb4b82ea74.
Report an issue: GitHub.