zellij-org/zellij · critical

Program terminates

Error message

Program terminates

What it means

Not an independent fault: FatalError::fatal() is zellij's terminal error handler, and 'Program terminates' is the expect fired when an anyhow::Result reaching .fatal() is Err. The method re-wraps the error with the context 'a fatal error occured' and unwraps it, intentionally aborting the process. Whatever io/spawn/serde failure flowed into that call site is the real cause; the ErrorContext call stack (Pty/Screen/Plugin/Client) logged just before the panic identifies it.

Source

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

}

/// Helper function to silence `#[warn(unused_must_use)]` cargo warnings. Used exclusively in
/// `FatalError::non_fatal`!
fn discard_result<T>(_arg: anyhow::Result<T>) {}

impl<T> FatalError<T> for anyhow::Result<T> {
    fn non_fatal(self) {
        if self.is_err() {
            discard_result(self.context("a non-fatal error occured").to_log());
        }
    }

    fn fatal(self) -> T {
        if let Ok(val) = self {
            val
        } else {
            self.context("a fatal error occured")
                .expect("Program terminates")
        }
    }
}

/// Different types of calls that form an [`ErrorContext`] call stack.
///
/// Complex variants store a variant of a related enum, whose variants can be built from
/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`],
/// [`PtyInstruction`], [`ClientInstruction`], etc).
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug)]
pub enum ContextType {
    /// A screen-related call.
    Screen(ScreenContext),
    /// A PTY-related call.
    Pty(PtyContext),
    /// A plugin-related call.
    Plugin(PluginContext),
    /// An app-related call.

View on GitHub (pinned to 98a0837077)

Solutions

  1. Read upwards from the panic: the ErrorContext stack and 'a fatal error occured' chain name the failing call and its cause
  2. Fix the underlying error at the call site it identifies (missing command, io failure, serialization mismatch)
  3. If the failure is genuinely unrecoverable, prefer exiting with the error printed rather than the generic panic text
  4. Search the exact ErrorContext text in zellij's issue tracker; these chains map to known failure modes

Example fix

// before
child.wait().with_context(|| err_context(&cmd)).fatal();

// after - branch on the error instead of unconditional termination
match child.wait().with_context(|| err_context(&cmd)) {
    Ok(status) => { /* ... */ }
    Err(err) => {
        log::error!("wait failed: {err:#}");
        quit_cb(PaneId::Terminal(terminal_id), None, cmd); // recover per-pane
    }
}
Defensive patterns

Strategy: try-catch

Try / catch

// before calling .fatal(), decide whether the error is truly terminal
match risky_operation().with_context(|| err_context(&cmd)) {
    Ok(val) => val,
    Err(err) if is_recoverable(&err) => {
        log::warn!("recoverable: {err:#}");
        default_or_retry()
    }
    Err(err) => {
        log::error!("fatal: {err:#}");
        std::process::exit(1); // explicit, message-carrying exit
    }
}

Prevention

When it happens

Trigger: Any `.fatal()` call site whose Result is Err - e.g. child.wait() or handle_command_exit failures on the pty path, io errors during teardown - after the anyhow context chain has been attached.

Common situations: Any zellij crash whose final line is 'Program terminates': the actionable cause is the ErrorContext/anyhow chain printed above it, not this message.

Related errors


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