BloopAI/vibe-kanban · error

invalid relay WS sequence: expected

Error message

invalid relay WS sequence: expected {expected_seq}, got {}

What it means

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.

Solutions

  1. Ensure envelopes are delivered in order and exactly once over the session
  2. Re-establish/re-key the crypto session after reconnects so both seq counters reset
  3. Drop duplicates before calling decode(); check envelope.seq vs inbound_seq before processing
  4. Debug which seq was received vs expected in the log message

Example fix

// before
let _ = crypto.decode(envelope_seq_5)?; // after already decoding seq 4, expected 5... got 5 fine; got 7 -> bail
let _ = crypto.decode(envelope_seq_7)?;
// after
let seq = envelope.seq;
if seq != crypto.expected_inbound_seq() { crypto.reset_session()?; }
let _ = crypto.decode(envelope)?;
Defensive patterns

Strategy: retry

Validate before calling

if envelope.seq != crypto.expected_inbound_seq() { // buffer or request retransmit before decode }

Try / catch

match crypto.decode(raw) { Err(e) if e.to_string().starts_with("invalid relay WS sequence") => resync_or_rekey_session(), other => other? }

Prevention

When it happens

Trigger: 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.

Common situations: Message replay/reordering across flaky transports; reconnect logic replaying buffered envelopes; mismatched session pairing where two encoders feed one decoder.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/4a0806ea22b7b938. Report an issue: GitHub.

Appendix: source

Thrown at crates/relay-ws/src/crypto.rs:95

        }
    }

    /// Verify a signed JSON envelope and deserialize it back into a frame.
    ///
    /// Checks the Ed25519 signature and enforces monotonic sequence ordering.
    pub(crate) fn decode(&mut self, raw: &[u8]) -> anyhow::Result<RelayWsFrame> {
        use anyhow::Context as _;

        let envelope: SignedWsEnvelope =
            serde_json::from_slice(raw).context("invalid relay WS envelope JSON")?;

        if envelope.version != ENVELOPE_VERSION {
            anyhow::bail!("unsupported relay WS envelope version");
        }

        let expected_seq = self.inbound_seq.saturating_add(1);
        if envelope.seq != expected_seq {
            anyhow::bail!(
                "invalid relay WS sequence: expected {expected_seq}, got {}",
                envelope.seq
            );
        }

        let payload = BASE64_STANDARD
            .decode(&envelope.payload_b64)
            .context("invalid relay WS payload")?;

        let signing_input = ws_signing_input(
            &self.request_signature,
            envelope.seq,
            envelope.msg_type,
            &payload,
        );
        let signature_bytes = BASE64_STANDARD
            .decode(&envelope.signature_b64)
            .context("invalid relay WS frame signature encoding")?;

View on GitHub (pinned to 4deb7eca8f)