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
- Verify the Fresh editor for the target session is actually running; restart it if it exited.
- Delete the stale control socket file for the session and reconnect so a fresh server re-creates it.
- Confirm the CLI and the editor are the same build/version (protocol_version must match).
- 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
- Confirm the editor process is running before connecting to its socket
- Delete stale socket files left by crashed editors
- Keep CLI and editor builds version-matched
- Log the server side around accept() to catch mid-handshake crashes
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- handshake with the Fresh editor failed
- Server error
- Unexpected server response
- cannot reach the Fresh editor for session
- Server closed connection
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)