zed-industries/zed · critical

Remote server exited with status {status}

Error message

Remote server exited with status {status}

What it means

Once the remote server is started in the container, Zed multiplexes RPC messages over its stdio; when the child terminates, the status is compared against 0 and any non-zero exit surfaces as this error. The number is zed-remote-server's raw exit status, meaning the server process itself died after launch.

Source

Thrown at crates/remote/src/transport/docker.rs:743

        let Ok(child) = command.spawn() else {
            return Task::ready(Err(anyhow::anyhow!(
                "Failed to start remote server process"
            )));
        };

        let mut proxy_process = self.proxy_process.lock();
        *proxy_process = Some(child.id());

        cx.spawn(async move |cx| {
            super::handle_rpc_messages_over_child_process_stdio(
                child,
                incoming_tx,
                outgoing_rx,
                connection_activity_tx,
                cx,
            )
            .await
            .and_then(|status| {
                if status != 0 {
                    anyhow::bail!("Remote server exited with status {status}");
                }
                Ok(0)
            })
        })
    }

    fn upload_directory(
        &self,
        src_path: PathBuf,
        dest_path: RemotePathBuf,
        cx: &App,
    ) -> Task<Result<()>> {
        let dest_path_str = dest_path.to_string();
        let src_path_display = src_path.display().to_string();

        let upload_task = Self::upload_and_chown(

View on GitHub (pinned to 5a9b9558db)

Solutions

  1. Run the binary directly in the container to see the loader/runtime error: `docker exec -it <id> <server_path> version` (or `ldd <server_path>`)
  2. Match architectures: pull the image for the container's real platform (`docker run --platform linux/arm64 ...`) or upload the matching server build
  3. Use a base image with a recent glibc or musl (debian bookworm, alpine) rather than ancient or empty bases
  4. Check whether the OOM killer terminated it: `docker inspect <id>` for OOMKilled, and raise the memory limit

Example fix

# before: amd64 image on arm64 host
FROM debian:bullseye

# after: match container platform to host/server build
FROM --platform=linux/arm64 debian:bookworm
Defensive patterns

Strategy: try-catch

Validate before calling

async fn server_binary_runs(conn: &DockerExecConnection, path: &str) -> bool {
    conn.run_docker_exec(path, None, &Default::default(), &["version"])
        .await
        .is_ok()
}

Try / catch

match rpc_task.await {
    Err(e) => {
        let msg = e.to_string();
        if let Some(status) = msg.strip_prefix("Remote server exited with status ") {
            // capture container logs / dmesg for OOM, run the binary manually to get the loader error
        }
        Err(e)
    }
    ok => ok,
}

Prevention

When it happens

Trigger: handle_rpc_messages_over_child_process_stdio observes the child exit with status != 0: dynamic linker failures (glibc/musl mismatch), wrong architecture binary, missing shared libraries, immediate crash on startup, or the container OOM-killing the process.

Common situations: x86_64 server binary inside an arm64 container (or vice versa, e.g. Apple Silicon host with amd64 image); very old glibc in the base image versus a newly built server; scratch images with no libc; container memory limits killing the server mid-session.

Related errors


AI-assisted analysis of zed-industries/zed@5a9b9558db (2026-08-20). Data as JSON: /api/errors/466d583230608f63. Report an issue: GitHub.