BloopAI/vibe-kanban · warning
invalid close payload
Error message
invalid close payload
What it means
When converting a relay WebSocket frame into an axum Close message, an empty payload means 'close without code', but any non-empty payload must carry at least a 2-byte big-endian status code (RFC 6455). Payloads of 1 byte (or a payload that later fails UTF-8 decoding) are rejected with this error.
Source
Thrown at crates/relay-protocol/src/lib.rs:85
};
RelayWsFrame { msg_type, payload }
}
fn try_from_frame(frame: RelayWsFrame) -> anyhow::Result<Self> {
match frame.msg_type {
RelayWsMessageType::Text => {
let text = String::from_utf8(frame.payload).context("invalid UTF-8 text frame")?;
Ok(Self::Text(text.into()))
}
RelayWsMessageType::Binary => Ok(Self::Binary(frame.payload.into())),
RelayWsMessageType::Ping => Ok(Self::Ping(frame.payload.into())),
RelayWsMessageType::Pong => Ok(Self::Pong(frame.payload.into())),
RelayWsMessageType::Close => {
if frame.payload.is_empty() {
return Ok(Self::Close(None));
}
if frame.payload.len() < 2 {
anyhow::bail!("invalid close payload");
}
let code = u16::from_be_bytes([frame.payload[0], frame.payload[1]]);
let reason = String::from_utf8(frame.payload[2..].to_vec())
.context("invalid UTF-8 close frame reason")?;
Ok(Self::Close(Some(AxumCloseFrame {
code,
reason: reason.into(),
})))
}
}
}
}
impl RelayTransportMessage for tungstenite::Message {
fn into_frame(self) -> RelayWsFrame {
let (msg_type, payload) = match self {
Self::Text(text) => (RelayWsMessageType::Text, text.to_string().into_bytes()),
Self::Binary(data) => (RelayWsMessageType::Binary, data.to_vec()),View on GitHub (pinned to 4deb7eca8f)
Solutions
- Fix the peer to send either an empty close payload or >=2 bytes (code + optional reason)
- Log and inspect raw payload length of offending frames
- Use Close(None) (empty payload) when no status code is intended
Example fix
// before frame.payload = vec![0x0a]; // 1 byte: invalid // after frame.payload = vec![0x0a, 0x00]; // 2-byte big-endian close code
Defensive patterns
Strategy: type-guard
Validate before calling
if !frame.payload.is_empty() && frame.payload.len() < 2 { log_warn("malformed close payload"); frame.payload.clear(); } Type guard
fn is_valid_close_payload(p: &[u8]) -> bool { p.is_empty() || p.len() >= 2 } Try / catch
match RelayCloseMessage::try_from_frame(&frame) { Err(e) if e.to_string() == "invalid close payload" => send_close(1002), ok => ok? } Prevention
- Send empty payload when no close code is needed
- Always encode close code as 2-byte big-endian
- Validate peer's WS implementation against RFC 6455
When it happens
Trigger: Receiving a Close frame whose payload is exactly 1 byte; try_from_frame called on a malformed relay Close frame.
Common situations: Peer implementation violating RFC 6455 close-frame encoding; a proxy mangling close frames; truncated frames from buggy serialization.
Related errors
- unsupported relay WS envelope version
- Server message has invalid length.
- Server message has invalid side identifier.
- request_id called for unsupported request variant
- Raw stream should only have Stdout/Stderr/Finished
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/f8919480b564db25.
Report an issue: GitHub.