vllm-project/vllm · error · Error

unexpected startup handshake message: {message}

Error message

unexpected startup handshake message: {message}

What it means

Error::UnexpectedHandshakeMessage is the catch-all for structurally invalid handshake traffic: wrong frame count (transport.rs:414-416), unexpected status strings (245, 295), duplicate HELLO/READY sequencing violations (204-292), registration from unexpected engine ids (493-497), and bootstrapped-mode arithmetic limits like engine_start_index not fitting u16 or engine_start_index + engine_count exceeding u16::MAX+1 (transport.rs:337-354). Also used by mock_engine.rs for peer identity issues.

Source

Thrown at rust/src/engine-core-client/src/error.rs:50

    InvalidStructuredOutputsParams { message: String },
    #[error("io error")]
    Io(#[from] std::io::Error),
    #[error("transport error")]
    Transport(#[from] zeromq::ZmqError),
    #[error("ZMQ runtime task failed")]
    ZmqRuntimeTask(#[from] tokio::task::JoinError),
    #[error("engine core reported fatal failure")]
    EngineCoreDead,
    #[error("startup handshake timed out while waiting for {stage} after {timeout:?}")]
    HandshakeTimeout {
        stage: &'static str,
        timeout: Duration,
    },
    #[error("engine input registration timed out after {timeout:?}")]
    InputRegistrationTimeout { timeout: Duration },
    #[error("unexpected engine id in startup handshake: expected {expected:?}, got {actual:?}")]
    UnexpectedHandshakeIdentity { expected: Vec<u8>, actual: Vec<u8> },
    #[error("unexpected startup handshake message: {message}")]
    UnexpectedHandshakeMessage { message: String },
    #[error("unexpected non-control output on coordinator path: {message}")]
    UnexpectedCoordinatorOutput { message: String },
    #[error("unexpected output on main dispatcher path: {message}")]
    UnexpectedDispatcherOutput { message: String },
    #[error("coordinator requires a Python-compatible two-byte engine id, got {engine_id:?}")]
    UnsupportedCoordinatorEngineId { engine_id: Vec<u8> },
    #[error("unsupported auxiliary frame(s): expected 1 frame, got {frame_count}")]
    UnsupportedAuxFrames { frame_count: usize },
    #[error("external coordinator mode is not implemented yet")]
    UnsupportedExternalCoordinator,
    #[error("unsupported field `{field}` in {context}")]
    UnsupportedField {
        context: &'static str,
        field: &'static str,
    },
    #[error("engine control channel closed unexpectedly: {message}")]
    ControlClosed { message: String },

View on GitHub (pinned to c794754062)

Solutions

  1. Read message: it states the exact protocol violation (frame count, status, engine id, or index overflow)
  2. Align Rust frontend and Python engine versions so the handshake protocol matches
  3. For index-overflow messages, reduce engine_count or lower engine_start_index in the supervisor config
  4. Capture traffic with RUST_LOG=engine_core_client=trace to see the offending frames
Defensive patterns

Strategy: try-catch

Validate before calling

fn bootstrapped_start_index_fits(start: u32, count: usize) -> Option<String> {
    u16::try_from(start).err().map(|_| "engine_start_index exceeds u16".into())
        .or_else(|| (start as usize + count > u16::MAX as usize + 1)
            .then(|| "start+count exceeds u16".into()))
}

Type guard

fn is_unexpected_handshake_message(e: &engine_core_client::Error) -> bool {
    matches!(e, engine_core_client::Error::UnexpectedHandshakeMessage { .. })
}

Try / catch

match result {
    Err(engine_core_client::Error::UnexpectedHandshakeMessage { message }) => {
        tracing::error!(message, "handshake protocol violation; likely engine/frontend version mismatch");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Any deviation from the HELLO→INIT→(gate)→READY→input-registration protocol: a frame that is not 2 parts, a status other than HELLO/READY, READY before the coordinator gate opens, duplicate HELLO after INIT, input registration from an id not in the pending set, or bootstrapped engine_start_index/engine_count outside the two-byte identity range.

Common situations: Version mismatch where the Python engine speaks an older/newer handshake protocol (e.g. pre/post vLLM commit c8d98f81). Race conditions at startup where a restarted engine re-HELLOs. Bootstrapped deployments with more than 65535 engines or a start index near u16::MAX — the message text names the exact violation.

Understand the failure class

Related errors


AI-assisted analysis of vllm-project/vllm@c794754062 (2026-08-14). Data as JSON: /api/errors/0b0461c388ac031f. Report an issue: GitHub.