{"record":{"id":"4a0806ea22b7b938","repo":"BloopAI/vibe-kanban","slug":"invalid-relay-ws-sequence-expected-expected-seq","errorCode":null,"errorMessage":"invalid relay WS sequence: expected {expected_seq}, got {}","messagePattern":"invalid relay WS sequence: expected (.+?), got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/relay-ws/src/crypto.rs","lineNumber":95,"sourceCode":"        }\n    }\n\n    /// Verify a signed JSON envelope and deserialize it back into a frame.\n    ///\n    /// Checks the Ed25519 signature and enforces monotonic sequence ordering.\n    pub(crate) fn decode(&mut self, raw: &[u8]) -> anyhow::Result<RelayWsFrame> {\n        use anyhow::Context as _;\n\n        let envelope: SignedWsEnvelope =\n            serde_json::from_slice(raw).context(\"invalid relay WS envelope JSON\")?;\n\n        if envelope.version != ENVELOPE_VERSION {\n            anyhow::bail!(\"unsupported relay WS envelope version\");\n        }\n\n        let expected_seq = self.inbound_seq.saturating_add(1);\n        if envelope.seq != expected_seq {\n            anyhow::bail!(\n                \"invalid relay WS sequence: expected {expected_seq}, got {}\",\n                envelope.seq\n            );\n        }\n\n        let payload = BASE64_STANDARD\n            .decode(&envelope.payload_b64)\n            .context(\"invalid relay WS payload\")?;\n\n        let signing_input = ws_signing_input(\n            &self.request_signature,\n            envelope.seq,\n            envelope.msg_type,\n            &payload,\n        );\n        let signature_bytes = BASE64_STANDARD\n            .decode(&envelope.signature_b64)\n            .context(\"invalid relay WS frame signature encoding\")?;","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/relay-ws/src/crypto.rs#L77-L113","documentation":"Signed WS envelopes carry a monotonically increasing sequence number; decode() requires the inbound envelope's seq to equal inbound_seq + 1 to detect replay, drops, or reordering under the negotiated crypto session. Any other sequence is rejected.","triggerScenarios":"Second call to decode() on a fresh CryptoSession reusing an old first envelope; out-of-order WS message delivery; duplicated envelope delivery; a peer restarting and resetting its seq counter while this side retains state.","commonSituations":"Message replay/reordering across flaky transports; reconnect logic replaying buffered envelopes; mismatched session pairing where two encoders feed one decoder.","solutions":["Ensure envelopes are delivered in order and exactly once over the session","Re-establish/re-key the crypto session after reconnects so both seq counters reset","Drop duplicates before calling decode(); check envelope.seq vs inbound_seq before processing","Debug which seq was received vs expected in the log message"],"exampleFix":"// before\nlet _ = crypto.decode(envelope_seq_5)?; // after already decoding seq 4, expected 5... got 5 fine; got 7 -> bail\nlet _ = crypto.decode(envelope_seq_7)?;\n// after\nlet seq = envelope.seq;\nif seq != crypto.expected_inbound_seq() { crypto.reset_session()?; }\nlet _ = crypto.decode(envelope)?;","handlingStrategy":"retry","validationCode":"if envelope.seq != crypto.expected_inbound_seq() { // buffer or request retransmit before decode }\n","typeGuard":null,"tryCatchPattern":"match crypto.decode(raw) { Err(e) if e.to_string().starts_with(\"invalid relay WS sequence\") => resync_or_rekey_session(), other => other? }","preventionTips":["Guarantee ordered delivery (use a single WS stream, no fan-out reordering)","Reset both seq counters on every reconnect/re-key","Drop duplicate envelopes before decode","Monitor sequence-gap logs to detect transport loss"],"tags":["crypto","sequence","websocket","replay-protection"],"backgroundTag":"sequence-number-mismatch","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}