{"record":{"id":"fb7baf571394bafc","repo":"tokio-rs/tokio","slug":"task-panicked","errorCode":null,"errorMessage":"task panicked","messagePattern":"task panicked","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"tokio/src/runtime/task/error.rs","lineNumber":173,"sourceCode":"impl fmt::Debug for JoinError {\n    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match &self.repr {\n            Repr::Cancelled => write!(fmt, \"JoinError::Cancelled({:?})\", self.id),\n            Repr::Panic(p) => match panic_payload_as_str(p) {\n                Some(panic_str) => {\n                    write!(fmt, \"JoinError::Panic({:?}, {:?}, ...)\", self.id, panic_str)\n                }\n                None => write!(fmt, \"JoinError::Panic({:?}, ...)\", self.id),\n            },\n        }\n    }\n}\n\nimpl std::error::Error for JoinError {}\n\nimpl From<JoinError> for io::Error {\n    fn from(src: JoinError) -> io::Error {\n        io::Error::new(\n            io::ErrorKind::Other,\n            match src.repr {\n                Repr::Cancelled => \"task was cancelled\",\n                Repr::Panic(_) => \"task panicked\",\n            },\n        )\n    }\n}\n\nfn panic_payload_as_str(payload: &SyncWrapper<Box<dyn Any + Send>>) -> Option<&str> {\n    // Panic payloads are almost always `String` (if invoked with formatting arguments)\n    // or `&'static str` (if invoked with a string literal).\n    //\n    // Non-string panic payloads have niche use-cases,\n    // so we don't really need to worry about those.\n    if let Some(s) = payload.downcast_ref_sync::<String>() {\n        return Some(s);\n    }","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/tokio-rs/tokio/blob/625954f365727668cb02d04172b34f1149637728/tokio/src/runtime/task/error.rs#L155-L191","documentation":"From<JoinError> for io::Error maps Repr::Panic(_) to the message 'task panicked'. It surfaces when a spawned task panics and the JoinHandle error is propagated as io::Error (e.g. via ?). The original panic payload is lost in this conversion; only the generic message survives.","triggerScenarios":"Awaiting a JoinHandle of a task that panicked, inside a function returning io::Result, so the JoinError converts to io::Error.","commonSituations":"unwrap()/expect() inside a spawned task; assertion failures; indexing out of bounds; propagating via ? which triggers From<JoinError>.","solutions":["Match on the JoinError and call into_panic()/resume_unwind to preserve and propagate the real panic.","Remove unwrap/expect from spawned tasks; use proper error returns.","Log the panic source via e.is_panic() before converting.","Separate task error handling from io::Result flows to avoid the lossy From conversion."],"exampleFix":"// before\nlet v = handle.await?; // 'task panicked' as io::Error\n// after\nmatch handle.await {\n    Ok(v) => v,\n    Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()),\n    Err(e) => return Err(e.into()),\n}","handlingStrategy":"try-catch","validationCode":"// Before awaiting, anticipate panic risk: design tasks to return Result<T, E>\n// rather than panicking.","typeGuard":"fn is_panic(e: &tokio::task::JoinError) -> bool { e.is_panic() }","tryCatchPattern":"match handle.await {\n    Ok(v) => v,\n    Err(e) if e.is_panic() => {\n        std::panic::resume_unwind(e.into_panic());\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Resume panics via into_panic()/resume_unwind to preserve the original payload.","Avoid unwrap/expect inside spawned tasks; return Result.","Log panic sources before any conversion."],"tags":["task","join-error","panic","io-error"],"backgroundTag":null,"analyzedSha":"625954f365727668cb02d04172b34f1149637728","analyzedAt":"2026-08-11T17:46:45.378Z","contentChangedAt":"2026-08-11T17:46:45.378Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}