Hmbown/CodeWhale · error

Task worker shutdown failed

Error message

Task worker shutdown failed: {error}

What it means

During task-manager shutdown, each spawned worker task's JoinHandle is awaited; if any worker task panicked or otherwise returned a JoinError, the manager records it and returns 'Task worker shutdown failed: {error}' after draining remaining workers so their exits are still observed.

Solutions

  1. Read the wrapped JoinError/panic message to find the panicking worker task
  2. Fix the underlying panic in the task body
  3. Update/rebuild if the panic comes from a known fixed bug
  4. Retry shutdown after normalizing state; check logs for the first panicking task
Defensive patterns

Strategy: try-catch

Try / catch

match manager.shutdown().await {
    Ok(()) => /* done */,
    Err(e) if e.to_string().starts_with("Task worker shutdown failed") => {
        log::error!("{e:#}"); // inspect JoinError/panic source, fix task body
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A worker task panicked or was canceled during shutdown; awaiting the JoinHandle returns Err(JoinError), which the drain loop converts into this anyhow error.

Common situations: A panic inside a background task (bug in task handling code); cancellation racing task completion; shutdown while a task is mid-operation on poisoned state.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/235bf1b99f285132. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/task_manager.rs:1569

        let _drain = self.shutdown_drain.lock().await;
        if let Some(runtime) = &self.runtime_threads {
            runtime.close_execution_admission().await;
        }
        // Waiting through the admission fence settles requests that passed
        // their admission check before shutdown was requested.
        {
            let _transaction = self.lock_store().await?;
        }
        let mut failure = None;
        {
            let mut workers = self.workers.lock().await;
            while let Some(worker) = workers.last_mut() {
                // Await by reference: canceling this caller leaves the join in
                // the manager, so a later drain still waits for actual exit.
                let result = worker.await;
                workers.pop();
                if let Err(error) = result {
                    failure = Some(anyhow!("Task worker shutdown failed: {error}"));
                }
            }
        }
        if let Some(runtime) = &self.runtime_threads {
            runtime.shutdown_and_wait().await?;
        }
        failure.map_or(Ok(()), Err)
    }

    pub(crate) fn execution_scope(&self) -> &str {
        &self.execution_lease.scope
    }

    pub(crate) fn session_store_binding(
        &self,
    ) -> Option<crate::runtime_threads::RuntimeStoreBinding> {
        self.runtime_threads
            .as_ref()

View on GitHub (pinned to 73e0f67d83)