Herdr Wire Protocol Version Negotiation: How the Handshake Works

Herdr uses an explicit binary handshake where the client sends a protocol version in a Hello message, and the server validates it against its own PROTOCOL_VERSION, rejecting mismatches with a descriptive error in the Welcome response.

The Herdr terminal multiplexer (ogulcancelik/herdr) implements a strict, integer-based versioning scheme in its binary wire protocol to prevent wire format incompatibilities between clients and the server. Understanding this negotiation flow is essential for developers building third-party clients or debugging connection failures in multi-version deployments.

The Version Handshake Flow

Herdr’s version negotiation follows a deterministic three-step handshake that occurs immediately after the Unix socket connection is established.

Client Initiates with Hello

The client must send a ClientMessage::Hello as its first message. This struct includes a version: u32 field representing the protocol version the client implements, alongside terminal dimensions and encoding preferences.

According to the source code in src/protocol/wire.rs (lines 67–85), the Hello message structure is defined as:

let hello = ClientMessage::Hello {
    version: PROTOCOL_VERSION,          // client's view of the protocol
    cols: 80,
    rows: 24,
    cell_width_px: 8,
    cell_height_px: 16,
    requested_encoding: RenderEncoding::SemanticFrame,
    keybindings: ClientKeybindings::Server,
    launch_mode: ClientLaunchMode::App,
};
protocol::write_message(&mut stream, &hello)?;

Server Reads and Validates

The server reads this initial message using generic framing functions in the client transport module. In src/server/client_transport.rs (lines 72–84), the server extracts the version field and passes it to protocol::check_client_version.

The validation logic resides in src/protocol/wire.rs (lines 24–32 and 624–642). Unlike protocols that support backward compatibility, Herdr enforces an exact match policy:

  • Version 0 (pre-persistence clients) is always rejected
  • Exact match with the server’s PROTOCOL_VERSION returns VersionCheck::Compatible
  • Client version newer than the server returns an incompatible error
  • Client version older than the server returns an incompatible error

The Welcome Response

Based on the validation result, the server constructs a ServerMessage::Welcome response:

  • Compatible: The server proceeds with initialization and sends a Welcome message with error: None (src/server/client_transport.rs, lines 35–41).
  • Incompatible: The server immediately sends a Welcome containing the server’s PROTOCOL_VERSION but sets error: Some(reason), then closes the connection (src/server/client_transport.rs, lines 85–93).

The server-side handling appears as follows:

match protocol::check_client_version(version) {
    protocol::VersionCheck::Compatible => {
        // continue with normal initialization
    }
    protocol::VersionCheck::Incompatible(reason) => {
        // Send a rejecting Welcome and close the connection
        let welcome = ServerMessage::Welcome {
            version: PROTOCOL_VERSION,
            encoding: RenderEncoding::SemanticFrame,
            error: Some(reason),
        };
        let _ = protocol::write_message(&mut stream, &welcome);
        return Ok(());
    }
}

Version Checking Implementation

The check_client_version function in src/protocol/wire.rs implements the negotiation policy with explicit error messages:

pub fn check_client_version(client_version: u32) -> VersionCheck {
    if client_version == 0 {
        return VersionCheck::Incompatible(
            "pre-persistence client (version 0) is not supported".to_owned(),
        );
    }

    if client_version == PROTOCOL_VERSION {
        VersionCheck::Compatible
    } else if client_version < PROTOCOL_VERSION {
        VersionCheck::Incompatible(format!(
            "client version {client_version} is older than server version {PROTOCOL_VERSION}; \
             please upgrade your herdr client"
        ))
    } else {
        VersionCheck::Incompatible(format!(
            "client version {client_version} is newer than server version {PROTOCOL_VERSION}; \
             please upgrade the herdr server"
        ))
    }
}

This design ensures no silent fallbacks occur. By rejecting all mismatches, Herdr avoids subtle bugs that could arise from differing wire formats between protocol versions.

Key Source Files

  • src/protocol/wire.rs: Defines PROTOCOL_VERSION, the ClientMessage::Hello structure, the VersionCheck enum, and the check_client_version function.
  • src/server/client_transport.rs: Handles the initial Hello read, invokes version validation, and manages the Welcome response lifecycle.

Summary

  • Herdr’s wire protocol requires clients to begin with a Hello message containing a version field.
  • The server validates this against its internal PROTOCOL_VERSION using check_client_version() in src/protocol/wire.rs.
  • Only exact version matches are accepted; version 0, older, and newer clients are explicitly rejected.
  • Incompatible connections receive a Welcome message with an error description before the server closes the socket.
  • This strict negotiation prevents wire format mismatches by failing fast during the handshake phase.

Frequently Asked Questions

What happens if the client version is newer than the server?

The server rejects the connection with a VersionCheck::Incompatible error containing the message "client version X is newer than server version Y; please upgrade the herdr server". The client receives this message in the error field of the Welcome response and should terminate with a clear upgrade instruction to the user.

Why does Herdr reject version 0 clients specifically?

Version 0 represents pre-persistence legacy clients that predate Herdr’s current storage model. According to src/protocol/wire.rs, these clients are unconditionally rejected with the message "pre-persistence client (version 0) is not supported" to prevent data corruption or undefined behavior with the current server architecture.

Where is the protocol version constant defined in the source code?

The PROTOCOL_VERSION constant is defined in src/protocol/wire.rs alongside the check_client_version function (lines 24–32 and 624–642). This file serves as the single source of truth for the wire format version used by both the client and server binaries.

How can a client detect that version negotiation failed?

The client reads the ServerMessage::Welcome response after sending its Hello. If the error field is Some(reason), the handshake failed and the connection will close. The client should display the error string to the user, which indicates whether the mismatch is due to an outdated client, outdated server, or unsupported legacy version.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →