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
- 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.
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
- 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.
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
- Server proof verification failed.
- Not authenticated
- Failed to accept invitation (${res.status})
- Failed to list organizations (${res.status})
- Failed to fetch identity (${res.status})
AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29).
Data as JSON: /api/errors/67a64954a330db0d.
Report an issue: GitHub.