{"record":{"id":"9bf16e53c8beaeca","repo":"zeroclaw-labs/zeroclaw","slug":"qq-websocket-connection-closed-internal-message-c","errorCode":null,"errorMessage":"QQ WebSocket connection closed: internal message channel closed","messagePattern":"QQ WebSocket connection closed: internal message channel closed","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/qq.rs","lineNumber":1825,"sourceCode":"                        .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(())\n    }\n}\n","sourceCodeStart":1807,"sourceCodeEnd":1843,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/qq.rs#L1807-L1843","documentation":"Raised by QQChannel::listen when the internal broadcast channel that carries parsed ChannelMessages to the consumer is closed (ExitReason::ChannelClosed) — tx.send(...) failed at qq.rs:1753 because the receiver was dropped. This is not a network or QQ problem: the downstream consumer (orchestrator/supervisor event loop) shut down while the gateway connection was still healthy. Unlike other exits, there is no WARN log, only the bail.","triggerScenarios":"listen()'s select arm forwards a composed ChannelMessage into tx; the receiving end of the mpsc channel has already been dropped (listener task cancelled, orchestrator shutting down, consumer erroring out), so send returns Err and the loop breaks with ExitReason::ChannelClosed.","commonSituations":"Graceful bot shutdown where the supervisor stops consuming before the channel task finishes; a consumer task that panicked or was cancelled; reconnect logic dropping the old listener's receiver without draining it.","solutions":["Treat it as a shutdown signal, not a failure — do not reconnect-loop on it; propagate so the caller can finish terminating","On graceful shutdown, drop/cancel the listener task at the same time as the consumer so this path is expected and quiet","If it appears unexpectedly mid-run, find which task owned the receiver and why it exited (check its logs/panics)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if let Err(err) = channel.listen(&tx).await {\n    if format!(\"{err:#}\").contains(\"internal message channel closed\") {\n        break; // consumer is gone: treat as shutdown, do NOT reconnect-loop\n    }\n    return Err(err);\n}","preventionTips":["Drop the listener task together with the consumer on shutdown so this exit is expected","Never auto-reconnect on this error — the new listener's messages would have nowhere to go","Investigate consumer panics/cancellations if this fires outside intentional shutdown"],"tags":["qq","websocket","channel-closed","shutdown","tokio-mpsc","lifecycle"],"backgroundTag":"channel-closed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}