{"record":{"id":"fc2930defc867b7d","repo":"zeroclaw-labs/zeroclaw","slug":"qq-websocket-connection-closed-write-failed","errorCode":null,"errorMessage":"QQ WebSocket connection closed: write failed","messagePattern":"QQ WebSocket connection closed: write failed","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/qq.rs","lineNumber":1822,"sourceCode":"                    WARN,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)\n                        .with_outcome(::zeroclaw_log::EventOutcome::Unknown)\n                        .with_attrs(::serde_json::json!({\"MAX_MISSED_ACKS\": MAX_MISSED_ACKS})),\n                    \"heartbeat timeout after consecutive missed ACKs; resume will be attempted on reconnect\"\n                );\n                anyhow::bail!(\n                    \"QQ WebSocket connection closed: heartbeat ACK timeout \\\n                     ({MAX_MISSED_ACKS} consecutive missed ACKs)\"\n                )\n            }\n            ExitReason::WriteFailed => {\n                ::zeroclaw_log::record!(\n                    WARN,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)\n                        .with_outcome(::zeroclaw_log::EventOutcome::Unknown),\n                    \"WebSocket write failed; resume will be attempted on reconnect\"\n                );\n                anyhow::bail!(\"QQ WebSocket connection closed: write failed\")\n            }\n            ExitReason::ChannelClosed => {\n                anyhow::bail!(\"QQ WebSocket connection closed: internal message channel closed\")\n            }\n        }\n    }\n\n    async fn health_check(&self) -> bool {\n        self.fetch_access_token_with_retry().await.is_ok()\n    }\n\n    async fn start_typing(&self, _recipient: &str) -> anyhow::Result<()> {\n        // No typing-indicator API on the QQ Bot Open Platform.\n        Ok(())\n    }\n\n    async fn stop_typing(&self, _recipient: &str) -> anyhow::Result<()> {\n        Ok(())","sourceCodeStart":1804,"sourceCodeEnd":1840,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/qq.rs#L1804-L1840","documentation":"Raised by QQChannel::listen when writing to the WebSocket fails (ExitReason::WriteFailed) — the outbound sink rejected a heartbeat Message::Text, a Pong reply to a server Ping, or another frame. A WARN is logged ('WebSocket write failed; resume will be attempted on reconnect') before the bail. It indicates the send side of the connection is broken even though reads may not have noticed yet.","triggerScenarios":"write.send(...) returns Err while answering a Ping with Pong (qq.rs:1523) or sending the op 1 heartbeat (qq.rs:1509): connection reset, TLS session torn down, buffer overflow after the peer stopped reading, or socket closed by an intermediary.","commonSituations":"Server or proxy closing the connection a moment before the client writes; network interface loss mid-write; long GC-like stalls in the host process causing the peer to give up and reset.","solutions":["No intervention needed per occurrence — the supervisor reconnects and Resume is attempted","If it appears in bursts, look for an intermediary (reverse proxy, VPN) killing long-lived WebSocket connections","Correlate with the heartbeat-timeout and close-frame errors to find who is dropping the connection first"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if let Err(err) = channel.listen(&tx).await {\n    if format!(\"{err:#}\").contains(\"write failed\") {\n        tokio::time::sleep(backoff.next()).await;\n        continue; // send side broke; reconnect and Resume\n    }\n    return Err(err);\n}","preventionTips":["Correlate with close-frame and heartbeat-timeout errors to find which side drops the connection first","Check intermediary WebSocket idle/timeout policies when write failures cluster","Reconnect promptly — a broken write side usually precedes a broken read side"],"tags":["qq","websocket","write-failed","broken-pipe","reconnect","network"],"backgroundTag":"websocket-write-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}