BloopAI/vibe-kanban · error

No peer key found for signing session {}

Error message

No peer key found for signing session {}

What it means

The signed WebSocket wrapper fetches the peer's verification key for the given signing session via signing.get_session_peer_key. If no key is stored for that signing_session_id, construction fails with 'No peer key found for signing session {}'. Without the peer key the connection's signatures cannot be verified, so the wrapper refuses to build.

Source

Thrown at crates/relay-ws/src/signed.rs:43

pub struct SignedWebSocket<S, M> {
    ws: S,
    signer: WsFrameSigner,
    verifier: WsFrameVerifier,
    _message: PhantomData<M>,
}

impl<S, M> SignedWebSocket<S, M> {
    async fn new(
        signing: &RelaySigningService,
        request_signature: &RequestSignature,
        ws: S,
    ) -> anyhow::Result<Self> {
        let peer_verify_key = signing
            .get_session_peer_key(request_signature.signing_session_id)
            .await
            .ok_or_else(|| {
                anyhow::anyhow!(
                    "No peer key found for signing session {}",
                    request_signature.signing_session_id
                )
            })?;
        Ok(Self {
            ws,
            signer: WsFrameSigner::new(request_signature, signing.clone()),
            verifier: WsFrameVerifier::new(request_signature, peer_verify_key),
            _message: PhantomData,
        })
    }
}

/// Wrap a tungstenite WebSocket stream into a signed channel.
///
/// Every outgoing frame is signed by [`WsFrameSigner::encode`].
/// Every incoming frame is verified by [`WsFrameVerifier::decode`].
pub async fn signed_tungstenite_websocket(

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Redo the signing handshake to register a fresh session key, then reconnect with the new signing_session_id.
  2. Ensure the relay uses a shared/persistent key store across instances so the key lookup succeeds regardless of which node handles the WS.
  3. Verify the signing_session_id in request_signature matches the one returned during key exchange (no stale cached signatures).
  4. Check key TTL/cleanup settings; increase session lifetime if connections are established long after handshake.

Example fix

// before
let sig = stale_cached_signature; // old signing_session_id after server restart
// after
let session = signing.begin_session(...).await?; // fresh exchange
let sig = session.sign_request(...);
SignedWs::new(signing, ws, sig).await?
Defensive patterns

Strategy: validation

Validate before calling

if signing.get_session_peer_key(sig.signing_session_id).await.is_none() {
    return Err(anyhow!(
        "signing session {} unknown; redo the key exchange before connecting",
        sig.signing_session_id
    ));
}

Type guard

async fn has_peer_key(signing: &Signing, session_id: Uuid) -> bool {
    signing.get_session_peer_key(session_id).await.is_some()
}

Try / catch

match SignedWs::new(signing, ws, sig).await {
    Err(e) if e.to_string().starts_with("No peer key found") => {
        tracing::warn!(%e, "stale signing session; re-running handshake");
        let sig = redo_signing_handshake().await?;
        SignedWs::new(signing, ws, sig).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Opening a signed WS connection with a request_signature whose signing_session_id was never registered (or already expired/cleaned up) in the signing key store; using a session id from a different relay instance or after a server restart wiped in-memory keys.

Common situations: Client completed the signing handshake against relay instance A but connects to instance B (load-balanced relay without shared key store); server restarted between key exchange and WS connect; stale/replayed signature from an expired session; clock/timeout causing session cleanup.

Related errors


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