zellij-org/zellij · critical
Process returned non-zero exit code: {}
Error message
Process returned non-zero exit code: {} What it means
Returned by spawn_server (Unix) when the freshly spawned `zellij --server <socket>` child process exits with a non-zero status. The parent only relays the exit code, so the real cause lives in the child's own stderr/log output. On Unix the server daemonizes via double-fork, so a non-zero status here means the server failed before or during daemonization.
Source
Thrown at zellij-client/src/lib.rs:467
/// On Unix the server daemonizes (double-fork) inside start_server(), so
/// the intermediate child exits immediately and `cmd.status()` returns.
#[cfg(not(windows))]
pub fn spawn_server(socket_path: &Path, debug: bool) -> io::Result<()> {
let mut cmd = Command::new(current_exe()?);
cmd.arg("--server").arg(socket_path);
if debug {
cmd.arg("--debug");
}
let status = cmd.status()?;
if status.success() {
Ok(())
} else {
let msg = "Process returned non-zero exit code";
let err_msg = match status.code() {
Some(c) => format!("{}: {}", msg, c),
None => msg.to_string(),
};
Err(io::Error::new(io::ErrorKind::Other, err_msg))
}
}
/// Spawn the Zellij server process.
///
/// On Windows there is no daemonize — we launch the server as a background
/// process with a hidden console. We use CREATE_NO_WINDOW (not
/// DETACHED_PROCESS) so the server gets valid standard handles;
/// DETACHED_PROCESS leaves stdin/stdout/stderr as NULL, which breaks PTY
/// creation, WASM plugin loading, and logging.
#[cfg(windows)]
pub fn spawn_server(socket_path: &Path, debug: bool) -> io::Result<()> {
use std::os::windows::process::CommandExt;
let mut cmd = Command::new(current_exe()?);
cmd.arg("--server").arg(socket_path);
if debug {
cmd.arg("--debug");
}View on GitHub (pinned to 98a0837077)
Solutions
- Run the server manually to see the real error: `zellij --server <socket-path> --debug` and read the log
- Remove stale session sockets (check `zellij list-sessions`; delete the offending socket dir, e.g. ZELLIJ_SOCKET_DIR entry)
- Set ZELLIJ_SOCKET_DIR to a writable location owned by your user, e.g. ZELLIJ_SOCKET_DIR=/tmp/zellij-$USER
- Ensure the zellij version is consistent (no half-upgraded binary + running server)
Defensive patterns
Strategy: retry
Validate before calling
// Before spawning, verify the environment the server needs:
use std::os::unix::fs::PermissionsExt;
let sock_dir = zellij_utils::shared::setuid::default_socket_dir(); // or your configured dir
let meta = std::fs::metadata(&sock_dir)?;
assert!(meta.is_dir());
assert!(meta.permissions().mode() & 0o700 != 0 || meta.uid() == unsafe { libc::getuid() }); Try / catch
match spawn_server(&socket_path, debug) {
Ok(()) => {}
Err(e) if e.to_string().contains("non-zero exit code") => {
eprintln!("server failed to start; run `zellij --server {} --debug` for details", socket_path.display());
std::process::exit(1);
},
Err(e) => return Err(e.into()),
} Prevention
- Clean stale sockets after crashes (zellij delete-all-session / remove the socket dir)
- Point ZELLIJ_SOCKET_DIR at a user-owned writable directory
- Upgrade client and server together; avoid mixing binaries across versions
- When startup fails, reproduce manually with `zellij --server <socket> --debug` and read the log
When it happens
Trigger: `zellij --server <socket_path>` exiting non-zero: unwritable/owned-by-another-user socket directory, a stale socket file conflicting with the new session, session name already in use, or a binary/version mismatch after an upgrade.
Common situations: Stale sockets in /tmp/zellij-<user> after a crash; ZELLIJ_SOCKET_DIR pointing at a read-only mount; upgrading zellij while an old server still runs; permissions (0700) failure on the socket dir.
Related errors
AI-assisted analysis of zellij-org/zellij@98a0837077 (2026-08-16).
Data as JSON: /api/errors/54b9ba474cacd034.
Report an issue: GitHub.