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
- Read upwards from the panic: the ErrorContext stack and 'a fatal error occured' chain name the failing call and its cause
- Fix the underlying error at the call site it identifies (missing command, io failure, serialization mismatch)
- If the failure is genuinely unrecoverable, prefer exiting with the error printed rather than the generic panic text
- 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
- Use .non_fatal() for errors the session can survive
- Log the full anyhow chain ({err:#}) before terminating
- Only call .fatal() at true process boundaries; per-pane/per-thread failures should degrade, not abort
- Correlate every fatal exit with its ErrorContext to build a failure catalog
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
- Could not find editor pane to replace - is no pane focused?
- Could not find editor pane to replace
- failed to find active pane id for client {client_id}
- no active pane for client {client_id}
- failed to find pane with id {pane_id:?}
AI-assisted analysis of zellij-org/zellij@98a0837077 (2026-08-16).
Data as JSON: /api/errors/4bb3d0a1533d54d8.
Report an issue: GitHub.