sinelaw/fresh · error

Server closed connection during handshake

Error message

Server closed connection during handshake

What it means

The Fresh CLI (`run_in_session`-style path) sent its Hello over the control socket, then `read_reply()` returned `None` — meaning the connection reached EOF before the server sent any reply. The error aborts the handshake instead of hanging or misparsing an empty response. Fresh throws it because a server that accepts and then closes is either the wrong program on the socket or a server that died mid-handshake.

Solutions

  1. Verify the Fresh editor for the target session is actually running; restart it if it exited.
  2. Delete the stale control socket file for the session and reconnect so a fresh server re-creates it.
  3. Confirm the CLI and the editor are the same build/version (protocol_version must match).
  4. Check the editor's logs for a panic or error right after the handshake to find why it closed.

Example fix

// before: blindly reusing a possibly stale socket
conn.write_control(&serde_json::to_string(&ClientControl::Hello(hello))?)?;
let response = read_reply()?.ok_or_else(|| anyhow::anyhow!("Server closed connection during handshake"))?;
// after: pre-flight check that a live server owns the socket
if !socket_is_alive(&socket_paths.control) {
    std::fs::remove_file(&socket_paths.control).ok();
    anyhow::bail!("no live Fresh editor for session '{}' (stale socket removed)", session);
}
conn.write_control(&serde_json::to_string(&ClientControl::Hello(hello))?)?;
let response = read_reply()?.ok_or_else(|| anyhow::anyhow!("Server closed connection during handshake"))?;
Defensive patterns

Strategy: retry

Validate before calling

fn socket_is_alive(p: &std::path::Path) -> bool { p.exists() && std::os::unix::net::UnixStream::connect(p).is_ok() }

Type guard

fn got_reply(r: &Option<String>) -> bool { r.is_some() }

Try / catch

match read_reply() {
    Ok(Some(resp)) => parse_handshake(resp),
    Ok(None) => { remove_stale_socket(); retry_with_fresh_daemon() }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the CLI subcommand that connects to an existing Fresh editor session's control socket when the peer closes the connection immediately after receiving `ClientControl::Hello` (read_reply() yields Ok(None)).

Common situations: Stale socket file left behind by a crashed editor; the listener process terminated between connect() and reply; connecting to a socket owned by a different/incompatible process; server crashed deserializing the Hello (e.g. protocol mismatch causing a panic).

Understand the failure class

Related errors


AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13). Data as JSON: /api/errors/5cc1e5b487249ec4. Report an issue: GitHub.

Appendix: source

Thrown at crates/fresh-editor/src/main.rs:3352

/// reader. The command-channel client passes a deadline-bounded one so a server
/// that accepts the connection and then goes quiet is an error, not a hang.
fn client_handshake_reading<F>(
    conn: &fresh::server::ipc::ClientConnection,
    mut read_reply: F,
) -> AnyhowResult<bool>
where
    F: FnMut() -> AnyhowResult<Option<String>>,
{
    use fresh::server::protocol::{
        ClientControl, ClientHello, ServerControl, TermSize, PROTOCOL_VERSION,
    };

    // Size doesn't matter — these clients never render.
    let hello = ClientHello::new(TermSize::new(80, 24));
    conn.write_control(&serde_json::to_string(&ClientControl::Hello(hello))?)?;

    let response = read_reply()?
        .ok_or_else(|| anyhow::anyhow!("Server closed connection during handshake"))?;

    match serde_json::from_str::<ServerControl>(&response)? {
        ServerControl::Hello(server_hello) => {
            if server_hello.protocol_version != PROTOCOL_VERSION {
                eprintln!(
                    "Version mismatch: server is v{}",
                    server_hello.server_version
                );
                return Ok(false);
            }
            Ok(true)
        }
        ServerControl::VersionMismatch(mismatch) => {
            eprintln!("Version mismatch: server is v{}", mismatch.server_version);
            Ok(false)
        }
        ServerControl::Error { message } => Err(anyhow::anyhow!("Server error: {}", message)),
        _ => Err(anyhow::anyhow!("Unexpected server response")),

View on GitHub (pinned to 67894ca546)