{"record":{"id":"084dded5595b0aa6","repo":"moghtech/komodo","slug":"connection-closed","errorCode":null,"errorMessage":"Connection closed","messagePattern":"Connection closed","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"lib/transport/src/websocket/mod.rs","lineNumber":92,"sourceCode":"    &mut self,\n  ) -> MaybeWithTimeout<\n    impl Future<Output = anyhow::Result<TransportMessage>> + Send,\n  > {\n    MaybeWithTimeout::new(async {\n      loop {\n        match tokio::time::timeout(\n          Duration::from_secs(10),\n          self.recv_inner(),\n        )\n        .await\n        .context(\"Timed out waiting for Ping\")??\n        {\n          WebsocketMessage::Message(message) => {\n            return message.decode();\n          }\n          WebsocketMessage::Ping => continue,\n          WebsocketMessage::Close => {\n            return Err(anyhow!(\"Connection closed\"));\n          }\n          WebsocketMessage::Closed => {\n            return Err(anyhow!(\"Connection already closed\"));\n          }\n        }\n      }\n    })\n  }\n}\n\nimpl<W: Websocket> WebsocketExt for W {}\n\n/// Traits for split websocket receiver\npub trait WebsocketSender {\n  /// Streamlined pinging\n  fn ping(\n    &mut self,\n  ) -> impl Future<Output = anyhow::Result<()>> + Send;","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/moghtech/komodo/blob/780ac68b992094a9fccd5fffb760e0c84fd3c3d1/lib/transport/src/websocket/mod.rs#L74-L110","documentation":"The websocket receive loop classifies each incoming frame: on a Close frame from the peer, recv_message returns this error to signal the connection was closed gracefully by the remote side. Subsequent logical message delivery is impossible until a reconnect is established.","triggerScenarios":"The remote endpoint sends a websocket Close frame while recv_message is awaiting the next message; e.g. the server shutting down, a reverse-proxy idle timeout, or the peer explicitly closing the session.","commonSituations":"Long-lived terminal/agent connections dropped after proxy idle timeouts; server restarts during deploys; the remote application calling close() after finishing its work.","solutions":["Treat this as terminal for the socket and re-establish the connection (reconnect loop with backoff)","Send periodic Ping frames from the client to keep intermediaries from closing idle connections","Handle the error variant explicitly to distinguish peer-initiated close from protocol errors","After reconnect, re-run any session/terminal channel setup that was tied to the old socket"],"exampleFix":"// before\nlet msg = ws.recv_message().await?;\n// after\nlet msg = match ws.recv_message().await {\n  Ok(m) => m,\n  Err(e) if e.to_string() == \"Connection closed\" => {\n    ws = reconnect().await?;\n    continue;\n  }\n  Err(e) => return Err(e),\n};","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async fn recv_with_reconnect(ws: &mut Ws) -> anyhow::Result<Message> {\n  loop {\n    match ws.recv_message().await {\n      Ok(m) => return m,\n      Err(e) if e.to_string() == \"Connection closed\" => {\n        *ws = connect_with_backoff().await?;\n        on_reconnected();\n      }\n      Err(e) => return Err(e),\n    }\n  }\n}","preventionTips":["Send client-side pings to defeat proxy idle timeouts","Implement automatic reconnect with exponential backoff","Re-initialize terminal channels after any reconnect","Distinguish 'Connection closed' (peer close) from other errors in your match arms"],"tags":["websocket","network","connection-closed","reconnect"],"backgroundTag":"connection-closed","analyzedSha":"780ac68b992094a9fccd5fffb760e0c84fd3c3d1","analyzedAt":"2026-09-08T10:02:44.861Z","contentChangedAt":"2026-09-08T10:02:44.861Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}