rathole-org/rathole · error

Protocol version mismatched. Expected

Error message

Protocol version mismatched. Expected {}, got {}. Please update `rathole`.

What it means

read_hello deserializes the peer's hello message and checks the embedded protocol version against CURRENT_PROTO_VERSION. When a ControlChannelHello carries a different version, it bails because control-channel peers must speak the same protocol. This guards against incompatible client/server builds.

Solutions

  1. Upgrade the older side: rebuild/update rathole on both client and server to the same version
  2. Pin matching versions (same release) on both ends of the tunnel
  3. Check rathole --version on both ends and align before restarting
  4. If upgrading is impossible, use compatible release channels on both sides

Example fix

// before
$ client: rathole v0.4.8  ↔  server: rathole v0.5.0  → version mismatch

// after
$ cargo install rathole  # same latest version on both hosts
$ client: rathole v0.5.0  ↔  server: rathole v0.5.0
Defensive patterns

Strategy: retry

Validate before calling

// Check version parity before connecting:
$ rathole --version
$ ssh server 'rathole --version'
// Both must print the same version.

Try / catch

// Retry with backoff only after fixing versions; a version mismatch never heals on retry:
match client_connect().await {
    Err(e) if e.to_string().contains("Protocol version mismatched") => {
        eprintln!("Upgrade both rathole ends to the same version, then restart");
        std::process::exit(1);
    }
    Err(e) => { /* transient error: retry with backoff */ }
    Ok(v) => v,
}

Prevention

When it happens

Trigger: Connecting a rathole client to a server built from a different (older or newer) version; the ControlChannelHello variant's version field differs from CURRENT_PROTO_VERSION in read_hello.

Common situations: Upgrading the server binary but not the client (or vice versa); a distro package pinned to an old rathole release talking to a recent server; cached old binaries in Docker images.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


AI-assisted analysis of rathole-org/rathole@a292f7ed54 (2026-09-07). Data as JSON: /api/errors/f9908a928fc7e223. Report an issue: GitHub.

Appendix: source

Thrown at src/protocol.rs:188

        }
    }
}

lazy_static! {
    static ref PACKET_LEN: PacketLength = PacketLength::new();
}

pub async fn read_hello<T: AsyncRead + AsyncWrite + Unpin>(conn: &mut T) -> Result<Hello> {
    let mut buf = vec![0u8; PACKET_LEN.hello];
    conn.read_exact(&mut buf)
        .await
        .with_context(|| "Failed to read hello")?;
    let hello = bincode::deserialize(&buf).with_context(|| "Failed to deserialize hello")?;

    match hello {
        Hello::ControlChannelHello(v, _) => {
            if v != CURRENT_PROTO_VERSION {
                bail!(
                    "Protocol version mismatched. Expected {}, got {}. Please update `rathole`.",
                    CURRENT_PROTO_VERSION,
                    v
                );
            }
        }
        Hello::DataChannelHello(v, _) => {
            if v != CURRENT_PROTO_VERSION {
                bail!(
                    "Protocol version mismatched. Expected {}, got {}. Please update `rathole`.",
                    CURRENT_PROTO_VERSION,
                    v
                );
            }
        }
    }

    Ok(hello)

View on GitHub (pinned to a292f7ed54)