zellij-org/zellij · critical
could not enable raw mode
Error message
could not enable raw mode
What it means
set_raw_mode() is called by the client on startup and expects crossterm::terminal::enable_raw_mode() to succeed: raw mode disables echo, line buffering and canonical signal processing so the client can read every key itself. It fails when the termios attributes cannot be read or written - stdin/stdout is not a tty, there is no controlling terminal, or the tty device is inaccessible. Because every keystroke flows through raw mode, the client treats this as fatal immediately.
Source
Thrown at zellij-client/src/os_input_output.rs:172
fn env_variable(&self, _name: &str) -> Option<String> {
None
}
/// Returns an async stdin reader that can be polled in tokio::select
fn get_async_stdin_reader(&self) -> Box<dyn AsyncStdin> {
Box::new(AsyncStdinReader::new())
}
/// Returns an async signal listener that can be polled in tokio::select
fn get_async_signal_listener(&self) -> io::Result<Box<dyn AsyncSignals>> {
Ok(Box::new(AsyncSignalListener::new()?))
}
}
impl ClientOsApi for ClientOsInputOutput {
fn get_terminal_size(&self) -> Size {
get_terminal_size()
}
fn set_raw_mode(&mut self) {
crossterm::terminal::enable_raw_mode().expect("could not enable raw mode");
}
fn unset_raw_mode(&self) -> Result<(), std::io::Error> {
crossterm::terminal::disable_raw_mode()
}
fn box_clone(&self) -> Box<dyn ClientOsApi> {
Box::new((*self).clone())
}
fn update_session_name(&mut self, new_session_name: String) {
*self.session_name.lock().unwrap() = Some(new_session_name);
}
fn read_from_stdin(&mut self) -> Result<Vec<u8>, &'static str> {
let session_name_at_calltime = { self.session_name.lock().unwrap().clone() };
// here we wait for a lock in case another thread is holding stdin
// this can happen for example when switching sessions, the old thread will only be
// released once it sees input over STDIN
//
// when this happens, we detect in the other thread that our session is ended (by comparing
// the session name at the beginning of the call and the one after we read from STDIN), andView on GitHub (pinned to 98a0837077)
Solutions
- Run zellij from a real interactive terminal with a tty on stdin and stdout
- Guard scripted launches with `[ -t 0 ] && [ -t 1 ]` or `tty -s` before starting zellij
- Wrap non-interactive contexts in a pty allocator such as `script -qec "zellij attach <name>" /dev/null`
- Fix tty device permissions or the stale controlling terminal (re-login) if it still fails interactively
Example fix
// before
fn set_raw_mode(&mut self) {
crossterm::terminal::enable_raw_mode().expect("could not enable raw mode");
}
// after - check the precondition, then make failure non-fatal
use std::io::IsTerminal;
fn set_raw_mode(&mut self) -> std::io::Result<()> {
if !std::io::stdin().is_terminal() {
return Err(std::io::Error::new(std::io::ErrorKind::NotConnected, "stdin is not a tty"));
}
crossterm::terminal::enable_raw_mode()
} Defensive patterns
Strategy: validation
Validate before calling
use std::io::IsTerminal;
fn has_usable_tty() -> bool {
std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
}
// before calling any client API that enters raw mode
if !has_usable_tty() {
eprintln!("zellij requires a tty on stdin and stdout");
std::process::exit(1);
} Prevention
- Never launch the client from cron/CI without a pty
- Check `[ -t 0 ] && [ -t 1 ]` in wrapper scripts
- Use ssh -tt / docker exec -it to force pty allocation
- Enable raw mode at start and disable it on every exit path, including panics
When it happens
Trigger: Launching the client with stdin or stdout redirected (scripts, cron, CI, `zellij < file`), running without a controlling terminal (detached setsid, some docker exec sessions), or a termios ioctl failure on the tty device.
Common situations: ssh -T; docker exec without -t; IDE task runners and watch tools that capture stdio; broken/absent TERM or tty permission problems on multi-user machines.
Related errors
- ClearScreenByPaneId missing pane_id
- 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}
AI-assisted analysis of zellij-org/zellij@98a0837077 (2026-08-16).
Data as JSON: /api/errors/d3fbf29130a2fc47.
Report an issue: GitHub.