sinelaw/fresh · error
Unexpected server response
Error message
Unexpected server response
What it means
The catch-all arm of the handshake response match in main.rs. After sending Hello, the CLI expects `Hello`, `VersionMismatch`, or `Error`; any other `ServerControl` variant reaches `_` and becomes this error. It signals that the peer speaks an unrecognized or corrupted message set rather than a known failure.
Solutions
- Check that client CLI and editor versions match (same PROTOCOL_VERSION / same build).
- Log the raw response string before parsing to see which variant actually arrived.
- Update both client and editor to the latest release so the ServerControl enum is identical.
- If a middle process sits on the socket, remove it and connect directly to the editor.
Example fix
// before
_ => Err(anyhow::anyhow!("Unexpected server response")),
// after: preserve the offending payload for diagnosis
_ => Err(anyhow::anyhow!("Unexpected server response: {}", response)), Defensive patterns
Strategy: validation
Validate before calling
const KNOWN: [&str;3] = ["Hello","VersionMismatch","Error"];
fn is_known_handshake_variant(raw: &str) -> bool {
KNOWN.iter().any(|k| raw.trim_start_matches('{').contains(k))
} Type guard
fn is_handshake_response(msg: &ServerControl) -> bool {
matches!(msg, ServerControl::Hello(_) | ServerControl::VersionMismatch(_) | ServerControl::Error { .. })
} Try / catch
if let Err(e) = handshake() {
if e.to_string().starts_with("Unexpected server response") {
eprintln!("protocol mismatch — verify client/server versions; raw: {raw}");
}
} Prevention
- Match PROTOCOL_VERSION between client and editor
- Log raw control frames before parsing to diagnose variant drift
- Upgrade both binaries together
- Never put non-handshake services on the editor's control socket
When it happens
Trigger: The server's first control message deserializes into a valid `ServerControl` but is none of Hello/VersionMismatch/Error — e.g. a newer protocol with extra variants, or a reply meant for a different request type.
Common situations: Client and server built from different commits so new variants exist on one side; a proxy or shim injecting unexpected control frames; a bug where the server skips the Hello reply.
Related errors
- handshake with the Fresh editor failed
- Server error
- no running Fresh editor for session
- server error
- server closed the connection before answering
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/791b78fa21bf0f5b.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-editor/src/main.rs:3370
.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")),
}
}
/// When launched from inside Fresh's own embedded terminal, forward the
/// file/dir arguments to the parent editor (identified by `FRESH_SESSION`)
/// instead of starting a second editor in the terminal.
///
/// Returns:
/// - `Some(Ok(()))` — forwarded; the caller should exit.
/// - `None` — not a nested launch, nothing to forward, or the parent
/// socket is unreachable; the caller should launch inline as usual.
fn try_forward_nested(args: &Args) -> Option<AnyhowResult<()>> {
// Only plain interactive file/dir opens are forwarded. Subcommands,
// --server and --attach are already handled before we get here;
// --stdin pipes content into a real editor and can't be forwarded.
if args.server || args.attach || args.stdin || args.files.is_empty() {
return None;
}View on GitHub (pinned to 67894ca546)