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

  1. Check that client CLI and editor versions match (same PROTOCOL_VERSION / same build).
  2. Log the raw response string before parsing to see which variant actually arrived.
  3. Update both client and editor to the latest release so the ServerControl enum is identical.
  4. 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

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


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)