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

  1. 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.
  2. 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).
  3. Reproduce with RUST_BACKTRACE=1 and file an issue including the panicked thread's backtrace plus this send context.
  4. 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

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


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)