zellij-org/zellij · error

failed to send message to channel

Error message

failed to send message to channel

What it means

The non-DEBUG_MODE variant of the same ToAnyhow impl for channel SendError<(T, ErrorContext)> in zellij-utils/src/errors.rs: the send failed because the receiver side was dropped, and the message payload is withheld in production builds. The attached ErrorContext string identifies the sending subsystem.

Solutions

  1. Confirm the target session/thread still exists (zellij list-sessions, process list for the zellij server).
  2. Re-run with DEBUG_MODE enabled to get variant [68] with the full undeliverable message — that shows exactly what was being sent when the channel died.
  3. Search logs upward for the first panic/exit — that closed the channel; report it with backtrace.
  4. Update zellij.
Defensive patterns

Strategy: try-catch

Try / catch

// production builds: same handling, generic message
match sender.send((msg, ErrorContext::new())).to_anyhow() {
    Ok(_) => {},
    Err(e) if e.to_string().starts_with("failed to send message to channel") => {
        log::warn!("peer thread exited; shutting down this worker");
        return;
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Identical mechanism to the debug variant: .send((msg, ErrorContext)).to_anyhow() on a channel whose consumer thread has exited (panic, teardown, or dropped receiver), with consts::DEBUG_MODE off so only the generic message is shown.

Common situations: Production/default builds where a session was killed while dependent threads still sent; a background thread panicked silently earlier; detach/exit races between client and server threads.

Related errors


AI-assisted analysis of zellij-org/zellij@bf8d23a4f7 (2026-08-19). Data as JSON: /api/errors/b82fc79589b0bcbb. Report an issue: GitHub.

Appendix: source

Thrown at zellij-utils/src/errors.rs:972

    ///
    /// 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) => {
                    if *crate::consts::DEBUG_MODE.get().unwrap_or(&true) {
                        Err(anyhow::anyhow!("cannot acquire poisoned lock for {e:#?}"))
                    } else {
                        Err(anyhow::anyhow!("cannot acquire poisoned lock"))
                    }
                },

View on GitHub (pinned to bf8d23a4f7)