tokio-rs/tokio · critical
A Tokio 1.x context was found, but IO is disabled. Call `ena
Error message
A Tokio 1.x context was found, but IO is disabled. Call `enable_io` on the runtime builder to enable IO.
What it means
Driver::io() calls .expect(...) when self.io is None. It panics because the runtime was built with the I/O driver disabled (no enable_io / not enable_all), yet code requested I/O driver access (tokio::net, AsyncFd, etc.).
Source
Thrown at tokio/src/runtime/driver.rs:94
}
}
impl Handle {
pub(crate) fn unpark(&self) {
#[cfg(feature = "time")]
if let Some(handle) = &self.time {
handle.unpark();
}
self.io.unpark();
}
cfg_io_driver! {
#[track_caller]
pub(crate) fn io(&self) -> &crate::runtime::io::Handle {
self.io
.as_ref()
.expect("A Tokio 1.x context was found, but IO is disabled. Call `enable_io` on the runtime builder to enable IO.")
}
}
cfg_signal_internal_and_unix! {
#[track_caller]
pub(crate) fn signal(&self) -> &crate::runtime::signal::Handle {
self.signal
.as_ref()
.expect("A Tokio 1.x context was found, but IO is disabled. Call `enable_io` on the runtime builder to enable IO.")
}
}
cfg_time! {
/// Returns a reference to the time driver handle.
///
/// Panics if no time driver is present.
#[track_caller]
pub(crate) fn time(&self) -> &crate::runtime::time::Handle {View on GitHub (pinned to 7d0d729d8f)
Solutions
- Call .enable_all() on the Builder (or .enable_io() explicitly).
- If using #[tokio::main], the default flavor already enables IO — don't pass a custom Builder that drops it.
- Audit all Builder chains for enable_io/enable_time/enable_signal.
- Isolate pure-compute tasks on a time-only runtime and run net code on a full runtime.
Example fix
// before
let rt = Builder::new_current_thread().enable_time().build()?;
rt.block_on(async { TcpStream::connect(...).await.unwrap(); }); // panics
// after
let rt = Builder::new_current_thread().enable_all().build()?;
rt.block_on(async { TcpStream::connect(...).await.unwrap(); }); Defensive patterns
Strategy: validation
Validate before calling
// Validate Builder configuration at construction time:
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all() // ensures IO driver is on
.build()?; Try / catch
// Panic cannot be caught across the macro; fix the Builder. If you must,
// catch_unwind around the runtime entry:
std::panic::catch_unwind(|| {
rt.block_on(async { TcpStream::connect(...).await })
}) Prevention
- Always call enable_all() (or enable_io()) on the Builder.
- Don't share compute-only runtimes with net code.
- Audit every Builder chain for IO/time/signal enablement.
When it happens
Trigger: Using TcpStream/UnixListener/AsyncFd on a runtime built via Builder without enable_io(); e.g. Builder::new_current_thread().enable_time().build() then performing networking.
Common situations: Custom Builder config forgetting enable_all; intentionally disabling IO for a CPU-only runtime then accidentally using a net API; test runtime missing IO.
Related errors
- there is no signal driver running, must be called from the c
- A Tokio 1.x context was found, but timers are disabled. Call
- provided length would overflow after adjustment
- background task failed
- background task failed
AI-assisted analysis of tokio-rs/tokio@7d0d729d8f (2026-08-11).
Data as JSON: /api/errors/04b44addcc75edbc.
Report an issue: GitHub.