zellij-org/zellij · critical
failed to accept reply connection
Error message
failed to accept reply connection
What it means
Windows-only accept path: after a client connects to the main ipc socket, the server accepts the paired reply connection on the second named pipe bound via ipc_bind_reply, and this expect treats an accept error as fatal. The failure is almost always a pipe-state race - the client died between connecting to the main socket and the reply accept, the pipe handle was closed, or third-party software interfered with the named pipe. It runs inside the per-connection branch of the listener loop, so one bad accept panics the whole server accept thread.
Source
Thrown at zellij-server/src/lib.rs:943
// It is not guaranteed that all platforms allow setting the sticky bit on sockets!
#[cfg(unix)]
drop(set_permissions(&socket_path, 0o1700));
// On Windows, named pipes are half-duplex, so we need a separate
// reply pipe for server→client messages.
#[cfg(windows)]
let reply_listener = zellij_utils::consts::ipc_bind_reply(&socket_path).unwrap();
for stream in listener.incoming() {
match stream {
Ok(stream) => {
let mut os_input = os_input.clone();
let client_id = session_state.write().unwrap().new_client();
#[cfg(windows)]
let reply_stream = reply_listener
.accept()
.expect("failed to accept reply connection");
#[cfg(windows)]
let receiver = os_input
.new_client_with_reply(client_id, stream, reply_stream)
.unwrap();
#[cfg(not(windows))]
let receiver = os_input.new_client(client_id, stream).unwrap();
let session_data = session_data.clone();
let session_state = session_state.clone();
let to_server = to_server.clone();
thread::Builder::new()
.name("server_router".to_string())
.spawn(move || {
route_thread_main(
session_data,
session_state,
os_input,View on GitHub (pinned to 98a0837077)
Solutions
- Clear stale zellij ipc artifacts (the zellij socket temp dir) and retry the attach
- Make sure client and server are the exact same zellij version - a mismatched handshake can drop the client
- Temporarily exclude zellij from antivirus/EDR named-pipe interception to test
- If the client itself crashes on connect, reproduce with logging and report it - the accept failure is then a symptom, not the cause
Example fix
// before
let reply_stream = reply_listener
.accept()
.expect("failed to accept reply connection");
// after - tolerate a lost client instead of killing the listener
let reply_stream = match reply_listener.accept() {
Ok(s) => s,
Err(e) => {
log::error!("reply accept failed ({e}); client likely vanished");
continue;
}
}; Defensive patterns
Strategy: retry
Try / catch
let mut attempts = 0;
let reply_stream = loop {
match reply_listener.accept() {
Ok(s) => break s,
Err(e) if attempts < 3 => {
attempts += 1;
log::warn!("reply accept retry {attempts}: {e}");
std::thread::sleep(std::time::Duration::from_millis(50));
}
Err(e) => {
log::error!("reply accept failed permanently: {e}; dropping this client");
continue; // drop this client, keep the listener alive
}
}
}; Prevention
- Clean the zellij socket temp directory after unclean shutdowns
- Pin client and server to the same zellij version
- Beware EDR/antivirus products that open named pipes concurrently
- Never let one connection's accept failure unwind the whole listener loop
When it happens
Trigger: A Windows client that connects to the main socket but crashes or exits before the reply pipe is accepted; a stale pipe object from a previous server; antivirus/EDR hooking named pipes and breaking the handshake.
Common situations: Attaching from a client that dies mid-handshake; leftover pipes in the temp dir after unclean shutdown; security software intercepting IPC.
Related errors
- PageScrollUpByPaneId missing pane_id
- PageScrollDownByPaneId missing pane_id
- HalfPageScrollUpByPaneId missing pane_id
- HalfPageScrollDownByPaneId missing pane_id
- ResizeByPaneId missing resize_action
AI-assisted analysis of zellij-org/zellij@98a0837077 (2026-08-16).
Data as JSON: /api/errors/c9014bc0e4d8fade.
Report an issue: GitHub.