astrid-runtime/astrid · error

Handshake response too large

Error message

Handshake response too large: {resp_len} bytes

What it means

send_request_read_response reads the handshake response's 4-byte length prefix and rejects any declared size above MAX_HANDSHAKE_RESPONSE_SIZE before allocating and reading the payload. Handshake responses are small by protocol; an oversized prefix indicates corruption, desync, or a hostile peer, so it fails fast.

Solutions

  1. Reconnect and retry the handshake to restore a clean stream state.
  2. Verify the request written before the read was correctly framed (length-prefixed); a bad request can corrupt the response stream.
  3. Align client and daemon versions so both use the same handshake framing and MAX_HANDSHAKE_RESPONSE_SIZE.
  4. If responses legitimately grew beyond the cap, raise MAX_HANDSHAKE_RESPONSE_SIZE on both sides.

Example fix

// before: writing the request without its length prefix desyncs the response framing
stream.write_all(&request_payload).await?;
// after
let mut framed = Vec::with_capacity(4 + request_payload.len());
framed.extend_from_slice(&(request_payload.len() as u32).to_be_bytes());
framed.extend_from_slice(&request_payload);
stream.write_all(&framed).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Sanity-bound the request you send; a corrupt request can corrupt the response stream
if request_payload.len() > MAX_HANDSHAKE_REQUEST_SIZE {
    return Err(anyhow::anyhow!("handshake request too large"));
}

Try / catch

match perform_handshake_in_home(&socket_path).await {
    Ok(auth) => auth,
    Err(e) if e.to_string().contains("Handshake response too large") => {
        // framing desynced: reconnect and retry once with a clean stream
        let mut client = reconnect(&socket_path).await?;
        perform_handshake_on(&mut client).await
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling send_request_read_response (via perform_handshake_in_home) when the response length prefix exceeds MAX_HANDSHAKE_RESPONSE_SIZE — e.g. the peer wrote raw payload without framing, the stream is misaligned after a failed write, or a garbage/broken response arrives.

Common situations: Handshake request was malformed so the daemon's error output (e.g. an HTML/log blob) is misread as a framed response; socket desynchronization from a previous oversized write; incompatible daemon version framing the response differently.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/7281cfee7bc841a5. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-uplink/src/socket_client.rs:614

    tokio::time::timeout(HANDSHAKE_TIMEOUT, async {
        stream.write_all(&len.to_be_bytes()).await?;
        stream.write_all(&request_bytes).await?;
        stream.flush().await?;
        Ok::<(), std::io::Error>(())
    })
    .await
    .context("Handshake request write timed out")?
    .context("Failed to send handshake request")?;

    let mut len_buf = [0u8; 4];
    tokio::time::timeout(HANDSHAKE_TIMEOUT, stream.read_exact(&mut len_buf))
        .await
        .context("Handshake response timed out")?
        .context("Failed to read handshake response length")?;

    let resp_len = u32::from_be_bytes(len_buf) as usize;
    if resp_len > MAX_HANDSHAKE_RESPONSE_SIZE {
        anyhow::bail!("Handshake response too large: {resp_len} bytes");
    }

    let mut resp_payload = vec![0u8; resp_len];
    tokio::time::timeout(HANDSHAKE_TIMEOUT, stream.read_exact(&mut resp_payload))
        .await
        .context("Handshake response payload timed out")?
        .context("Failed to read handshake response payload")?;

    serde_json::from_slice(&resp_payload).context("Failed to parse handshake response")
}

View on GitHub (pinned to affd8760f4)