{"record":{"id":"2baf8d8ad9104d59","repo":"zeroclaw-labs/zeroclaw","slug":"qq-websocket-connection-closed-close-code-code","errorCode":null,"errorMessage":"QQ WebSocket connection closed: close_code={code}, reason=\"{reason}\"","messagePattern":"QQ WebSocket connection closed: close_code=(.+?), reason=\"(.+?)\"","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/qq.rs","lineNumber":1789,"sourceCode":"                *self.session_id.write().await = None;\n                *self.last_sequence.write().await = None;\n                anyhow::bail!(\n                    \"QQ WebSocket connection closed: invalid session (fresh auth required)\"\n                )\n            }\n            ExitReason::Reconnect => {\n                // Session state preserved — supervisor will reconnect and we'll attempt Resume\n                anyhow::bail!(\n                    \"QQ WebSocket connection closed: server requested reconnect (resume will be attempted)\"\n                )\n            }\n            ExitReason::Close(ref frame) => {\n                let (code, reason) = frame\n                    .as_ref()\n                    .map(|f| (f.code.to_string(), f.reason.to_string()))\n                    .unwrap_or_else(|| (\"unknown\".into(), \"none\".into()));\n                ::zeroclaw_log::record!(WARN, ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note).with_outcome(::zeroclaw_log::EventOutcome::Unknown).with_attrs(::serde_json::json!({\"code\": code.to_string(), \"reason\": reason.to_string()})), \"WebSocket closed with code=, reason=\\\"\\\"; resume will be attempted on reconnect\");\n                anyhow::bail!(\n                    \"QQ WebSocket connection closed: close_code={code}, reason=\\\"{reason}\\\"\"\n                )\n            }\n            ExitReason::StreamEnded => {\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 stream ended unexpectedly; resume will be attempted on reconnect\"\n                );\n                anyhow::bail!(\"QQ WebSocket connection closed: stream ended unexpectedly\")\n            }\n            ExitReason::HeartbeatTimeout => {\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                        .with_attrs(::serde_json::json!({\"MAX_MISSED_ACKS\": MAX_MISSED_ACKS})),","sourceCodeStart":1771,"sourceCodeEnd":1807,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/qq.rs#L1771-L1807","documentation":"Raised by QQChannel::listen when the WebSocket read loop receives a Close frame (ExitReason::Close). The handler extracts the numeric close code and reason string from the frame ('unknown'/'none' if the frame is absent) and emits a WARN log before bailing, so the tuple of (code, reason) is available in both the structured log and the error message. Recovery follows the reconnect path — Resume is attempted on the next listen() since session state is kept.","triggerScenarios":"The QQ gateway closes the socket with a Close frame: authentication close codes (e.g. 4004 invalid token / 4010-4012 shard or version problems in QQ-style gateways), rate-limit closes, or a normal 1000 server shutdown. frame.as_ref() Some => (code, reason); None => 'unknown'/'none'.","commonSituations":"Invalid or expired QQ bot credentials producing an auth close code right after connecting; gateway maintenance closes; intermediaries (proxies, load balancers) injecting close frames on idle; version mismatch between the client's gateway protocol and the server.","solutions":["Read the close code: 1000/1001 and server-maintenance reasons just need a reconnect; auth codes (4004-style) mean credentials must be fixed first","For auth close codes, verify AppID/AppSecret and token acquisition, then reconnect","Keep the supervisor reconnecting with backoff — Resume preserves the session across most closes","If closes repeat immediately after connect, check for a competing connection with the same credentials or a gateway protocol version change"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if let Err(err) = channel.listen(&tx).await {\n    let msg = format!(\"{err:#}\");\n    if msg.contains(\"close_code=4004\") || msg.contains(\"close_code=4010\") {\n        return Err(err); // auth/shard close codes: stop reconnecting until credentials are fixed\n    }\n    tokio::time::sleep(backoff.next()).await;\n    continue; // other codes: reconnect and Resume\n}","preventionTips":["Branch on the close_code in the message — auth codes need credential fixes, others just reconnect","Keep the WARN structured log (code, reason attrs) searchable to correlate recurring close codes","Ensure intermediaries (proxies/LBs) permit long-lived WebSocket sessions"],"tags":["qq","websocket","close-frame","reconnect","gateway","channel"],"backgroundTag":"websocket-close-frame","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}