zellij-org/zellij · critical

failed to receive event on channel

Error message

failed to receive event on channel

What it means

Identical failure mode in the pty thread: pty_thread_main loops on pty.bus.recv() and expects a PtyInstruction forever. recv() errors when all senders are gone - normally only during teardown ordering races or after another thread's panic dropped its sender end of the bus. Because the loop cannot proceed without instructions, the expect panics and takes the pty thread, and effectively the session, with it.

Source

Thrown at zellij-server/src/pty.rs:216

pub(crate) struct Pty {
    pub active_panes: HashMap<ClientId, PaneId>,
    pub bus: Bus<PtyInstruction>,
    pub id_to_child_pid: HashMap<u32, u32>, // terminal_id => child pid
    originating_plugins: HashMap<u32, OriginatingPlugin>,
    debug_to_file: bool,
    task_handles: HashMap<u32, JoinHandle<()>>, // terminal_id to join-handle
    default_editor: Option<PathBuf>,
    post_command_discovery_hook: Option<String>,
    plugin_cwds: HashMap<u32, PathBuf>,   // plugin_id -> cwd
    terminal_cwds: HashMap<u32, PathBuf>, // terminal_id -> cwd
    pane_activity_flags: HashMap<u32, std::sync::Arc<std::sync::atomic::AtomicBool>>,
    terminal_cmds: HashMap<u32, Vec<String>>,
    terminal_foreground_cmds: HashMap<u32, Vec<String>>,
}

pub(crate) fn pty_thread_main(mut pty: Pty, layout: Box<Layout>) -> Result<()> {
    loop {
        let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");
        err_ctx.add_call(ContextType::Pty((&event).into()));
        match event {
            PtyInstruction::SpawnTerminal(
                terminal_action,
                name,
                new_pane_placement,
                start_suppressed,
                client_or_tab_index,
                completion_tx,
                set_blocking,
            ) => {
                let err_context =
                    || format!("failed to spawn terminal for {:?}", client_or_tab_index);

                let (hold_on_close, run_command, pane_title, open_file_payload) =
                    match &terminal_action {
                        Some(TerminalAction::RunCommand(run_command)) => (
                            run_command.hold_on_close,

View on GitHub (pinned to 98a0837077)

Solutions

  1. Start a new session; the panicked one cannot be resumed reliably
  2. Update zellij for pty-thread shutdown fixes
  3. Check the log for an earlier panic - this error is usually the echo of another fault
  4. Capture the sequence with debug logging if it reproduces and report it

Example fix

// before
let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");

// after - exit the loop gracefully on disconnect
let (event, mut err_ctx) = match pty.bus.recv() {
    Ok(recv) => recv,
    Err(_) => {
        log::info!("pty bus disconnected; exiting pty thread");
        break;
    }
};
Defensive patterns

Strategy: fallback

Try / catch

loop {
    let (event, mut err_ctx) = match pty.bus.recv() {
        Ok(recv) => recv,
        Err(_) => {
            log::info!("pty bus closed; stopping pty thread without panic");
            break;
        }
    };
    err_ctx.add_call(ContextType::Pty((&event).into()));
    // handle event...
}

Prevention

When it happens

Trigger: Session exit while SpawnTerminal/ClosePane instructions are still queued; an earlier server-thread panic disconnecting the bus under the pty loop.

Common situations: Rapid layout changes followed by exit; cascade after an unrelated server panic; shutdown sequencing changes between zellij versions.

Related errors


AI-assisted analysis of zellij-org/zellij@98a0837077 (2026-08-16). Data as JSON: /api/errors/653e3d137e517191. Report an issue: GitHub.