{"record":{"id":"140361fc4eecefc6","repo":"zeroclaw-labs/zeroclaw","slug":"irc-connection-closed-by-server","errorCode":null,"errorMessage":"IRC connection closed by server","messagePattern":"IRC connection closed by server","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/irc.rs","lineNumber":479,"sourceCode":"\n        loop {\n            line.clear();\n            let n = tokio::time::timeout(READ_TIMEOUT, buf_reader.read_line(&mut line))\n                .await\n                .map_err(|_| {\n                    ::zeroclaw_log::record!(\n                        WARN,\n                        ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Timeout)\n                            .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                            .with_attrs(::serde_json::json!({\n                                \"timeout\": format!(\"{:?}\", READ_TIMEOUT),\n                            })),\n                        \"irc: read timed out\"\n                    );\n                    anyhow::Error::msg(format!(\"IRC read timed out (no data for {READ_TIMEOUT:?})\"))\n                })??;\n            if n == 0 {\n                anyhow::bail!(\"IRC connection closed by server\");\n            }\n\n            let Some(msg) = IrcMessage::parse(&line) else {\n                continue;\n            };\n\n            match msg.command.as_str() {\n                \"PING\" => {\n                    let token = msg.params.first().map_or(\"\", String::as_str);\n                    let mut guard = self.writer.lock().await;\n                    if let Some(ref mut w) = *guard {\n                        Self::send_raw(w, &format!(\"PONG :{token}\")).await?;\n                    }\n                }\n\n                // CAP responses for SASL\n                \"CAP\" if sasl_pending && msg.params.iter().any(|p| p.contains(\"sasl\")) => {\n                    if msg.params.iter().any(|p| p.contains(\"ACK\")) {","sourceCodeStart":461,"sourceCodeEnd":497,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/irc.rs#L461-L497","documentation":"In IRC listen's read loop, BufReader::read_line returning 0 bytes means the server closed the TLS stream (EOF); the loop bails immediately. It sits right after a READ_TIMEOUT wrapper, so this error specifically means data ended (FIN/RST), not silence. IRC servers close connections for ping-timeout (client failed PONG), KILL, excess flood, netsplits, or restarts — the caller is expected to treat listen's error as 'reconnect'.","triggerScenarios":"Any point during listen(): the server sends FIN after the client missed PING/PONG cadence (e.g. process paused, NAT idle timeout dropped the mapping), an operator KILLs the client, the nick collides terminally, or the IRCd restarts. read_line returns Ok(0) and the bail fires before IrcMessage parsing.","commonSituations":"Long-running bots behind NAT/firewalls that drop idle TLS sessions; laptop sleep/resume resuming a dead socket; aggressive anti-idle server settings; scheduled IRCd maintenance.","solutions":["Treat this error as reconnect-worthy in the supervisor: back off briefly and re-enter listen() (registration, SASL, JOIN re-run)","If disconnects recur quickly, check whether the server sent an ERROR/KILL line just before EOF (enable raw line logging) and address that reason","Keep-alive harden: ensure PING/PONG handling stays responsive (the loop answers PING with PONG) and consider a lightweight periodic activity source if the network drops idle clients","Use exponential backoff on repeated immediate EOFs to avoid hammering the server"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"loop {\n    if let Err(e) = irc.listen(tx.clone()).await {\n        if e.to_string().contains(\"closed by server\") || e.to_string().contains(\"read timed out\") {\n            tokio::time::sleep(backoff.next()).await; // reconnect: registration + JOIN re-run\n            continue;\n        }\n        return Err(e); // e.g. 464 password mismatch: terminal, do not loop\n    }\n}","preventionTips":["Wrap listen() in a reconnect supervisor with exponential backoff; EOF is expected over long uptimes","Keep PONG responses prompt (the loop already answers PING) and avoid blocking the task between ticks","Distinguish EOF (retry) from auth numerics like 464 (fail fast) in your error handling"],"tags":["irc","disconnect","eof","reconnect","network"],"backgroundTag":"connection-closed-by-peer","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}