sinelaw/fresh · error

handshake with the Fresh editor failed

Error message

handshake with the Fresh editor failed

What it means

After connecting to the editor's control socket, the client performs a protocol handshake via client_handshake. If the editor does not accept (protocol/version or capability mismatch), the client bails with this generic message; the specific mismatch reason was already printed by client_handshake to stderr.

Solutions

  1. Read the mismatch reason printed just before this error on stderr.
  2. Restart the editor so both sides run the same Fresh version.
  3. Ensure the `fresh` on PATH is the same build as the running editor (`which fresh`, `fresh --version`).
  4. Reinstall/upgrade so client and editor versions match.

Example fix

# before
~/.cargo/bin/fresh --cmd split right   # old client vs new editor
# after
hash -r; fresh --version && fresh --cmd split right   # same binary as running editor
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure client and editor versions match before connecting
const { execSync } = require('child_process');
const cliV = execSync('fresh --version').toString().trim();
const srvV = execSync('fresh --cmd version').toString().trim();
if (cliV !== srvV) { console.error(`version mismatch: cli=${cliV} editor=${srvV}; restart editor`); process.exit(1); }

Try / catch

// rust
match connect_and_handshake(&paths) {
    Ok(conn) => conn,
    Err(e) if e.to_string().contains("handshake") => {
        eprintln!("version mismatch with running editor; restart it and retry");
        std::process::exit(2);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A fresh CLI client whose protocol version/capabilities differ from the running editor's, so the editor rejects the CmdConnection during handshake.

Common situations: Editor upgraded (or downgraded) while an old binary is still running and newer/older CLI invokes it; mixing two Fresh versions on PATH (e.g. one from cargo install, one distro package); connecting a stale CLI to a long-lived editor session.

Understand the failure class

Related errors


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

Appendix: source

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

    let conn = fresh::server::ipc::ClientConnection::connect(socket_paths).map_err(|e| {
        if e.kind() == std::io::ErrorKind::PermissionDenied {
            socket_denied_error(
                &std::env::var("FRESH_SESSION").unwrap_or_else(|_| "?".to_string()),
                socket_paths,
            )
        } else {
            anyhow::Error::from(e)
        }
    })?;
    let mut reader = conn.control_reader();
    let accepted = client_handshake_reading(&conn, || {
        reader
            .read_line_timeout(cmd_reply_timeout())
            .map_err(cmd_read_error)
    })?;
    if !accepted {
        // client_handshake already printed the mismatch reason.
        anyhow::bail!("handshake with the Fresh editor failed");
    }
    Ok(CmdConnection { conn, reader })
}

/// Read control replies until a `ScriptResult` arrives (or an error/EOF/timeout).
/// Returns `(ok, error, output)`.
fn read_script_result(
    conn: &mut CmdConnection,
    timeout: std::time::Duration,
) -> AnyhowResult<(bool, Option<String>, Option<String>)> {
    use fresh::server::protocol::ServerControl;
    loop {
        match conn.recv_within(timeout)? {
            Some(ServerControl::ScriptResult { ok, error, output }) => {
                return Ok((ok, error, output))
            }
            Some(ServerControl::Error { message }) => anyhow::bail!("server error: {}", message),
            Some(_) => continue,

View on GitHub (pinned to 67894ca546)