{"record":{"id":"089ab950eca48809","repo":"BloopAI/vibe-kanban","slug":"relay-control-channel-closed","errorCode":null,"errorMessage":"Relay control channel closed","messagePattern":"Relay control channel closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/relay-tunnel-core/src/client.rs","lineNumber":65,"sourceCode":"\n    let ws_io = tungstenite_ws_stream_io(ws_stream);\n    let mut session = Session::new_client(ws_io, yamux_config());\n    let mut control = session.control();\n\n    tracing::debug!(\"Relay control channel connected\");\n\n    let shutdown = config.shutdown;\n    let local_addr = config.local_addr;\n\n    loop {\n        tokio::select! {\n            _ = shutdown.cancelled() => {\n                control.close().await;\n                return Ok(());\n            }\n            inbound = session.next() => {\n                let stream = inbound\n                    .ok_or_else(|| anyhow::anyhow!(\"Relay control channel closed\"))?\n                    .map_err(|e| anyhow::anyhow!(\"Relay yamux session error: {e}\"))?;\n\n                tokio::spawn(async move {\n                    if let Err(error) = handle_inbound_stream(stream, local_addr).await {\n                        tracing::warn!(?error, \"Relay stream handling failed\");\n                    }\n                });\n            }\n        }\n    }\n}\n\nasync fn handle_inbound_stream(\n    stream: tokio_yamux::StreamHandle,\n    local_addr: SocketAddr,\n) -> anyhow::Result<()> {\n    let io = TokioIo::new(stream);\n","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/relay-tunnel-core/src/client.rs#L47-L83","documentation":"The relay client select! loop reads inbound streams from the yamux control session. When session.next() returns None, the yamux session (control channel) has been closed by the peer or locally, so no more inbound streams can arrive; the client then errors with 'Relay control channel closed' instead of hanging.","triggerScenarios":"The relay server closes the control connection (restart, idle timeout, crash), the WebSocket/underlying transport to the relay drops, or yamux tears down the session after a protocol error.","commonSituations":"Relay server redeployed or restarted while clients were connected; network interruption (NAT timeout, VPN drop); client stayed idle past a server-side keepalive limit; mismatched protocol versions causing the server to hang up.","solutions":["Restart/reconnect: call start_relay again to establish a fresh control channel (implement automatic reconnection with backoff in the caller).","Check the relay server is running and reachable; inspect server logs for why it closed the session.","Ensure keepalive/heartbeat settings on the yamux session and underlying transport are configured to survive idle periods.","Verify client and relay server versions are compatible."],"exampleFix":"// before\nlet stream = inbound.ok_or_else(|| anyhow::anyhow!(\"Relay control channel closed\"))?;\n// after (caller side)\nloop {\n    if let Err(e) = start_relay(...).await {\n        tracing::warn!(?e, \"relay client ended; reconnecting in 2s\");\n        tokio::time::sleep(Duration::from_secs(2)).await;\n    }\n}","handlingStrategy":"retry","validationCode":"// before connecting, confirm the relay is reachable\nlet reachable = tokio::net::TcpStream::connect(relay_addr).await.is_ok();\nif !reachable {\n    return Err(anyhow!(\"relay {} unreachable; not starting client\", relay_addr));\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = start_relay(...).await {\n    if e.to_string().contains(\"Relay control channel closed\") {\n        tracing::warn!(?e, \"relay closed; reconnecting with backoff\");\n        backoff_retry(|| start_relay(...)).await?;\n    } else {\n        return Err(e);\n    }\n}","preventionTips":["Implement automatic reconnect with exponential backoff around start_relay.","Enable yamux/transport keepalives to detect dead connections early.","Monitor relay server restarts and version deploys.","Surface relay connection state in the UI for faster diagnosis."],"tags":["network","relay","yamux","connection-closed"],"backgroundTag":"control-channel-closed","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}