{"record":{"id":"67a64954a330db0d","repo":"BloopAI/vibe-kanban","slug":"no-peer-key-found-for-signing-session","errorCode":null,"errorMessage":"No peer key found for signing session {}","messagePattern":"No peer key found for signing session (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/relay-ws/src/signed.rs","lineNumber":43,"sourceCode":"\npub struct SignedWebSocket<S, M> {\n    ws: S,\n    signer: WsFrameSigner,\n    verifier: WsFrameVerifier,\n    _message: PhantomData<M>,\n}\n\nimpl<S, M> SignedWebSocket<S, M> {\n    async fn new(\n        signing: &RelaySigningService,\n        request_signature: &RequestSignature,\n        ws: S,\n    ) -> anyhow::Result<Self> {\n        let peer_verify_key = signing\n            .get_session_peer_key(request_signature.signing_session_id)\n            .await\n            .ok_or_else(|| {\n                anyhow::anyhow!(\n                    \"No peer key found for signing session {}\",\n                    request_signature.signing_session_id\n                )\n            })?;\n        Ok(Self {\n            ws,\n            signer: WsFrameSigner::new(request_signature, signing.clone()),\n            verifier: WsFrameVerifier::new(request_signature, peer_verify_key),\n            _message: PhantomData,\n        })\n    }\n}\n\n/// Wrap a tungstenite WebSocket stream into a signed channel.\n///\n/// Every outgoing frame is signed by [`WsFrameSigner::encode`].\n/// Every incoming frame is verified by [`WsFrameVerifier::decode`].\npub async fn signed_tungstenite_websocket(","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/relay-ws/src/signed.rs#L25-L61","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Redo the signing handshake to register a fresh session key, then reconnect with the new signing_session_id.","Ensure the relay uses a shared/persistent key store across instances so the key lookup succeeds regardless of which node handles the WS.","Verify the signing_session_id in request_signature matches the one returned during key exchange (no stale cached signatures).","Check key TTL/cleanup settings; increase session lifetime if connections are established long after handshake."],"exampleFix":"// before\nlet sig = stale_cached_signature; // old signing_session_id after server restart\n// after\nlet session = signing.begin_session(...).await?; // fresh exchange\nlet sig = session.sign_request(...);\nSignedWs::new(signing, ws, sig).await?","handlingStrategy":"validation","validationCode":"if signing.get_session_peer_key(sig.signing_session_id).await.is_none() {\n    return Err(anyhow!(\n        \"signing session {} unknown; redo the key exchange before connecting\",\n        sig.signing_session_id\n    ));\n}","typeGuard":"async fn has_peer_key(signing: &Signing, session_id: Uuid) -> bool {\n    signing.get_session_peer_key(session_id).await.is_some()\n}","tryCatchPattern":"match SignedWs::new(signing, ws, sig).await {\n    Err(e) if e.to_string().starts_with(\"No peer key found\") => {\n        tracing::warn!(%e, \"stale signing session; re-running handshake\");\n        let sig = redo_signing_handshake().await?;\n        SignedWs::new(signing, ws, sig).await?;\n    }\n    other => other?,\n}","preventionTips":["Always obtain a fresh signing_session_id from the handshake immediately before connecting.","Use a shared/persistent key store when running multiple relay instances.","Never cache or replay request signatures across server restarts.","Set key TTLs longer than the worst-case connect delay."],"tags":["auth","cryptography","relay","websocket","signing"],"backgroundTag":"missing-signing-key","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}