zellij-org/zellij · error
failed to send message to channel
Error message
failed to send message to channel: {:#?} What it means
Produced by the ToAnyhow impl for Result<_, SendError<(T, ErrorContext)>> in zellij-utils/src/errors.rs when a cross-thread channel send fails because the receiver was dropped. The DEBUG_MODE variant additionally pretty-prints the undeliverable message. The ErrorContext attached as anyhow context tells you which subsystem owned the channel.
Solutions
- Check whether the zellij session still exists (zellij list-sessions) — if it died, the send failure is a symptom; reconnect or start a new session.
- Look earlier in the log for the original panic or exit of the receiving thread — the poison point, not this send error, is the root cause (set ZELLIJ_LOG_FILE / run with DEBUG_MODE for the verbose message).
- Reproduce with RUST_BACKTRACE=1 and file an issue including the panicked thread's backtrace plus this send context.
- Update zellij; shutdown-order races between threads are fixed regularly.
Defensive patterns
Strategy: try-catch
Try / catch
// sending across zellij channels: handle receiver-gone explicitly
use zellij_utils::errors::ToAnyhow;
if let Err(e) = sender.send((msg, ErrorContext::new())).to_anyhow() {
if e.to_string().starts_with("failed to send message to channel") {
// receiving thread is gone: session teardown or its panic
log::warn!("channel closed, stopping sender loop: {e:#}");
return; // exit this loop instead of retrying
}
return Err(e);
} Prevention
- Stop sender loops as soon as a send fails with a closed channel; never retry a send to a dropped receiver.
- Tie channel endpoints to thread lifetimes (drop senders/receivers together) so teardown closes both sides deterministically.
- When DEBUG_MODE is on, use the printed undeliverable message to identify which subsystem's receiver died.
When it happens
Trigger: Calling .to_anyhow() (or the usual .send(...).to_anyhow() combinators) on a channel whose receiving thread already exited: the thread panicked, the session/thread was torn down, or the receiver was dropped due to a lifecycle bug. Typical senders include client- and plugin-threads talking to the server router/input threads.
Common situations: Session killed from another terminal while a client thread still sends; a worker thread (rendering, OS input, plugin host) panicked earlier and its channel closed; shutdown races during detach/exit; plugin thread exited while the server still routes events to it.
Related errors
- failed to send message to channel
- cannot acquire poisoned lock
- cannot acquire poisoned lock for
- Character key needs character data
- Failed to find active pane
AI-assisted analysis of zellij-org/zellij@bf8d23a4f7 (2026-08-19).
Data as JSON: /api/errors/4b735172a91423eb.
Report an issue: GitHub.
Appendix: source
Thrown at zellij-utils/src/errors.rs:966
}
/// `SendError` doesn't satisfy `anyhow`s trait requirements due to `T` possibly being a
/// `PluginInstruction` type, which wraps an `mpsc::Send` and isn't `Sync`. Due to this, in turn,
/// the whole error type isn't `Sync` and doesn't work with `anyhow` (or pretty much any other
/// error handling crate).
///
/// Takes the `SendError` and creates an `anyhow` error type with the message that was sent
/// (formatted as string), attaching the [`ErrorContext`] as anyhow context to it.
impl<T: std::fmt::Debug, U> ToAnyhow<U>
for Result<U, crate::channels::SendError<(T, ErrorContext)>>
{
fn to_anyhow(self) -> anyhow::Result<U> {
match self {
Ok(val) => anyhow::Ok(val),
Err(e) => {
let (msg, context) = e.into_inner();
if *crate::consts::DEBUG_MODE.get().unwrap_or(&true) {
Err(anyhow::anyhow!(
"failed to send message to channel: {:#?}",
msg
))
.with_context(|| context.to_string())
} else {
Err(anyhow::anyhow!("failed to send message to channel"))
.with_context(|| context.to_string())
}
},
}
}
}
impl<U> ToAnyhow<U> for Result<U, std::sync::PoisonError<U>> {
fn to_anyhow(self) -> anyhow::Result<U> {
match self {
Ok(val) => anyhow::Ok(val),
Err(e) => {View on GitHub (pinned to bf8d23a4f7)