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

  1. Run the server manually to see the real error: `zellij --server <socket-path> --debug` and read the log
  2. Remove stale session sockets (check `zellij list-sessions`; delete the offending socket dir, e.g. ZELLIJ_SOCKET_DIR entry)
  3. Set ZELLIJ_SOCKET_DIR to a writable location owned by your user, e.g. ZELLIJ_SOCKET_DIR=/tmp/zellij-$USER
  4. 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

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.