moghtech/komodo · warning · anyhow::Error
Connection closed
Error message
Connection closed
What it means
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.
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
Example fix
// before
let msg = ws.recv_message().await?;
// after
let msg = match ws.recv_message().await {
Ok(m) => m,
Err(e) if e.to_string() == "Connection closed" => {
ws = reconnect().await?;
continue;
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: retry
Try / catch
async fn recv_with_reconnect(ws: &mut Ws) -> anyhow::Result<Message> {
loop {
match ws.recv_message().await {
Ok(m) => return m,
Err(e) if e.to_string() == "Connection closed" => {
*ws = connect_with_backoff().await?;
on_reconnected();
}
Err(e) => return Err(e),
}
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Long-lived terminal/agent connections dropped after proxy idle timeouts; server restarts during deploys; the remote application calling close() after finishing its work.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
AI-assisted analysis of moghtech/komodo@780ac68b99 (2026-09-08).
Data as JSON: /api/errors/084dded5595b0aa6.
Report an issue: GitHub.
Appendix: source
Thrown at lib/transport/src/websocket/mod.rs:92
&mut self,
) -> MaybeWithTimeout<
impl Future<Output = anyhow::Result<TransportMessage>> + Send,
> {
MaybeWithTimeout::new(async {
loop {
match tokio::time::timeout(
Duration::from_secs(10),
self.recv_inner(),
)
.await
.context("Timed out waiting for Ping")??
{
WebsocketMessage::Message(message) => {
return message.decode();
}
WebsocketMessage::Ping => continue,
WebsocketMessage::Close => {
return Err(anyhow!("Connection closed"));
}
WebsocketMessage::Closed => {
return Err(anyhow!("Connection already closed"));
}
}
}
})
}
}
impl<W: Websocket> WebsocketExt for W {}
/// Traits for split websocket receiver
pub trait WebsocketSender {
/// Streamlined pinging
fn ping(
&mut self,
) -> impl Future<Output = anyhow::Result<()>> + Send;View on GitHub (pinned to 780ac68b99)