tokio-rs/tokio · error
Can't get a task id when not inside a task
Error message
Can't get a task id when not inside a task
What it means
task::id() calls context::current_task_id().expect('Can\'t get a task id when not inside a task'). It panics when invoked outside a spawned task — including from block_on bodies, which do not carry a task ID. The doc note explicitly warns block_on has no task ID.
Source
Thrown at tokio/src/runtime/task/id.rs:46
/// [`AbortHandle`]: crate::task::AbortHandle
/// [`JoinSet`]: crate::task::JoinSet
#[cfg_attr(docsrs, doc(cfg(all(feature = "rt"))))]
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub struct Id(pub(crate) NonZeroU64);
/// Returns the [`Id`] of the currently running task.
///
/// # Panics
///
/// This function panics if called from outside a task. Please note that calls
/// to `block_on` do not have task IDs, so the method will panic if called from
/// within a call to `block_on`. For a version of this function that doesn't
/// panic, see [`task::try_id()`](crate::runtime::task::try_id()).
///
/// [task ID]: crate::task::Id
#[track_caller]
pub fn id() -> Id {
context::current_task_id().expect("Can't get a task id when not inside a task")
}
/// Returns the [`Id`] of the currently running task, or `None` if called outside
/// of a task.
///
/// This function is similar to [`task::id()`](crate::runtime::task::id()), except
/// that it returns `None` rather than panicking if called outside of a task
/// context.
///
/// [task ID]: crate::task::Id
#[track_caller]
pub fn try_id() -> Option<Id> {
context::current_task_id()
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)View on GitHub (pinned to 625954f365)
Solutions
- Use tokio::task::try_id() which returns Option<Id> instead of panicking.
- Call task::id() only inside futures spawned via tokio::spawn / spawn_local.
- If you need an ID in the block_on body, spawn the work as a task and await its JoinHandle.
- Guard instrumentation code with try_id() and fall back to a synthetic ID.
Example fix
// before
runtime.block_on(async {
let id = tokio::task::id(); // panics: not inside a task
});
// after
runtime.block_on(async {
let id = tokio::task::try_id(); // Option<Id>
});
// or
runtime.block_on(async {
let h = tokio::spawn(async { tokio::task::id() });
let id = h.await.unwrap();
}); Defensive patterns
Strategy: type-guard
Validate before calling
// Use the non-panicking variant when context is uncertain: let id = tokio::task::try_id(); // Option<Id>
Type guard
fn current_id_or_none() -> Option<tokio::task::Id> {
tokio::task::try_id()
} Try / catch
// For instrumentation that may run outside a task:
let label = tokio::task::try_id()
.map(|id| id.to_string())
.unwrap_or_else(|| "no-task".to_string()); Prevention
- Default to try_id() in library code.
- Only call id() inside futures spawned via tokio::spawn.
- Wrap block_on body work in tokio::spawn if you need a task ID.
When it happens
Trigger: Calling tokio::task::id() from the top-level future inside runtime.block_on, from a non-runtime thread, or from a synchronous context.
Common situations: Using task::id() inside the async fn passed to #[tokio::main] (the main future is block_on, not a task); calling from a thread that did not enter a task context; logging/metrics code that assumes it always runs in a task.
Related errors
- task panicked
- There must be more than one worker
- `JoinError` reason is not a panic.
- cannot create LocalSet during thread shutdown
- Unhandled Panic behavior modified after starting LocalSet
AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11).
Data as JSON: /api/errors/df9f99bfb1458b1e.
Report an issue: GitHub.