{"record":{"id":"5cc1e5b487249ec4","repo":"sinelaw/fresh","slug":"server-closed-connection-during-handshake","errorCode":null,"errorMessage":"Server closed connection during handshake","messagePattern":"Server closed connection during handshake","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-editor/src/main.rs","lineNumber":3352,"sourceCode":"/// reader. The command-channel client passes a deadline-bounded one so a server\n/// that accepts the connection and then goes quiet is an error, not a hang.\nfn client_handshake_reading<F>(\n    conn: &fresh::server::ipc::ClientConnection,\n    mut read_reply: F,\n) -> AnyhowResult<bool>\nwhere\n    F: FnMut() -> AnyhowResult<Option<String>>,\n{\n    use fresh::server::protocol::{\n        ClientControl, ClientHello, ServerControl, TermSize, PROTOCOL_VERSION,\n    };\n\n    // Size doesn't matter — these clients never render.\n    let hello = ClientHello::new(TermSize::new(80, 24));\n    conn.write_control(&serde_json::to_string(&ClientControl::Hello(hello))?)?;\n\n    let response = read_reply()?\n        .ok_or_else(|| anyhow::anyhow!(\"Server closed connection during handshake\"))?;\n\n    match serde_json::from_str::<ServerControl>(&response)? {\n        ServerControl::Hello(server_hello) => {\n            if server_hello.protocol_version != PROTOCOL_VERSION {\n                eprintln!(\n                    \"Version mismatch: server is v{}\",\n                    server_hello.server_version\n                );\n                return Ok(false);\n            }\n            Ok(true)\n        }\n        ServerControl::VersionMismatch(mismatch) => {\n            eprintln!(\"Version mismatch: server is v{}\", mismatch.server_version);\n            Ok(false)\n        }\n        ServerControl::Error { message } => Err(anyhow::anyhow!(\"Server error: {}\", message)),\n        _ => Err(anyhow::anyhow!(\"Unexpected server response\")),","sourceCodeStart":3334,"sourceCodeEnd":3370,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor/src/main.rs#L3334-L3370","documentation":"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.","triggerScenarios":"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)).","commonSituations":"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).","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."],"exampleFix":"// before: blindly reusing a possibly stale socket\nconn.write_control(&serde_json::to_string(&ClientControl::Hello(hello))?)?;\nlet response = read_reply()?.ok_or_else(|| anyhow::anyhow!(\"Server closed connection during handshake\"))?;\n// after: pre-flight check that a live server owns the socket\nif !socket_is_alive(&socket_paths.control) {\n    std::fs::remove_file(&socket_paths.control).ok();\n    anyhow::bail!(\"no live Fresh editor for session '{}' (stale socket removed)\", session);\n}\nconn.write_control(&serde_json::to_string(&ClientControl::Hello(hello))?)?;\nlet response = read_reply()?.ok_or_else(|| anyhow::anyhow!(\"Server closed connection during handshake\"))?;","handlingStrategy":"retry","validationCode":"fn socket_is_alive(p: &std::path::Path) -> bool { p.exists() && std::os::unix::net::UnixStream::connect(p).is_ok() }","typeGuard":"fn got_reply(r: &Option<String>) -> bool { r.is_some() }","tryCatchPattern":"match read_reply() {\n    Ok(Some(resp)) => parse_handshake(resp),\n    Ok(None) => { remove_stale_socket(); retry_with_fresh_daemon() }\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["network","handshake","ipc","unix-socket"],"backgroundTag":"connection-closed-by-server","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}