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
- Read the wrapped JoinError/panic message to find the panicking worker task
- Fix the underlying panic in the task body
- Update/rebuild if the panic comes from a known fixed bug
- 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
- Avoid panics in worker task bodies; return Result instead
- Keep worker loops responsive to cancellation
- Check logs for the first panicking task to find the root cause
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
- ChatGPT revoke task was lost
- dispatch task was lost
- engine event channel closed before turn
- MCP connection task panicked
- parent cancellation fan-in
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)