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

  1. Use tokio::task::try_id() which returns Option<Id> instead of panicking.
  2. Call task::id() only inside futures spawned via tokio::spawn / spawn_local.
  3. If you need an ID in the block_on body, spawn the work as a task and await its JoinHandle.
  4. 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

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


AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11). Data as JSON: /api/errors/df9f99bfb1458b1e. Report an issue: GitHub.