{"record":{"id":"f8919480b564db25","repo":"BloopAI/vibe-kanban","slug":"invalid-close-payload","errorCode":null,"errorMessage":"invalid close payload","messagePattern":"invalid close payload","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/relay-protocol/src/lib.rs","lineNumber":85,"sourceCode":"        };\n        RelayWsFrame { msg_type, payload }\n    }\n\n    fn try_from_frame(frame: RelayWsFrame) -> anyhow::Result<Self> {\n        match frame.msg_type {\n            RelayWsMessageType::Text => {\n                let text = String::from_utf8(frame.payload).context(\"invalid UTF-8 text frame\")?;\n                Ok(Self::Text(text.into()))\n            }\n            RelayWsMessageType::Binary => Ok(Self::Binary(frame.payload.into())),\n            RelayWsMessageType::Ping => Ok(Self::Ping(frame.payload.into())),\n            RelayWsMessageType::Pong => Ok(Self::Pong(frame.payload.into())),\n            RelayWsMessageType::Close => {\n                if frame.payload.is_empty() {\n                    return Ok(Self::Close(None));\n                }\n                if frame.payload.len() < 2 {\n                    anyhow::bail!(\"invalid close payload\");\n                }\n                let code = u16::from_be_bytes([frame.payload[0], frame.payload[1]]);\n                let reason = String::from_utf8(frame.payload[2..].to_vec())\n                    .context(\"invalid UTF-8 close frame reason\")?;\n                Ok(Self::Close(Some(AxumCloseFrame {\n                    code,\n                    reason: reason.into(),\n                })))\n            }\n        }\n    }\n}\n\nimpl RelayTransportMessage for tungstenite::Message {\n    fn into_frame(self) -> RelayWsFrame {\n        let (msg_type, payload) = match self {\n            Self::Text(text) => (RelayWsMessageType::Text, text.to_string().into_bytes()),\n            Self::Binary(data) => (RelayWsMessageType::Binary, data.to_vec()),","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/relay-protocol/src/lib.rs#L67-L103","documentation":"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.","triggerScenarios":"Receiving a Close frame whose payload is exactly 1 byte; try_from_frame called on a malformed relay Close frame.","commonSituations":"Peer implementation violating RFC 6455 close-frame encoding; a proxy mangling close frames; truncated frames from buggy serialization.","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"],"exampleFix":"// before\nframe.payload = vec![0x0a]; // 1 byte: invalid\n// after\nframe.payload = vec![0x0a, 0x00]; // 2-byte big-endian close code","handlingStrategy":"type-guard","validationCode":"if !frame.payload.is_empty() && frame.payload.len() < 2 { log_warn(\"malformed close payload\"); frame.payload.clear(); }","typeGuard":"fn is_valid_close_payload(p: &[u8]) -> bool { p.is_empty() || p.len() >= 2 }","tryCatchPattern":"match RelayCloseMessage::try_from_frame(&frame) { Err(e) if e.to_string() == \"invalid close payload\" => send_close(1002), ok => ok? }","preventionTips":["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"],"tags":["websocket","protocol","close-frame"],"backgroundTag":"invalid-close-frame","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}