{"record":{"id":"9b162a093f93cf0a","repo":"zeroclaw-labs/zeroclaw","slug":"qq-websocket-connection-closed-invalid-session-f","errorCode":null,"errorMessage":"QQ WebSocket connection closed: invalid session (fresh auth required)","messagePattern":"QQ WebSocket connection closed: invalid session \\(fresh auth required\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/qq.rs","lineNumber":1773,"sourceCode":"                                exit_reason = ExitReason::ChannelClosed;\n                                break 'outer;\n                            }\n                        }\n                        _ => {}\n                    }\n                }\n            }\n        }\n\n        // Persist sequence number for potential resume on next reconnect\n        *self.last_sequence.write().await = if sequence >= 0 { Some(sequence) } else { None };\n\n        match exit_reason {\n            ExitReason::InvalidSession => {\n                // Clear stored session so next reconnect does a fresh Identify\n                *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                )","sourceCodeStart":1755,"sourceCodeEnd":1791,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/qq.rs#L1755-L1791","documentation":"Raised by QQChannel::listen when its WebSocket gateway loop exits with ExitReason::InvalidSession — the server declared the session invalid (op 9 after Identify, or a rejected Resume). Before bailing, the handler clears session_id and last_sequence so the next listen() call performs a fresh Identify instead of a doomed Resume. This is a recoverable by-design error: the supervisor reconnects and re-authenticates from scratch, but events during the gap are not replayed.","triggerScenarios":"listen() attempts Resume with a session_id that the QQ gateway no longer recognizes (disconnect longer than the resume window), or receives an invalid-session reply right after Identify. The match arm at qq.rs:1769 clears session state and bails.","commonSituations":"Bot resumed after suspend, deploy, or a long network partition with a stale session id; QQ invalidating sessions during platform maintenance; two instances running with the same QQ bot credentials, each invalidating the other's session.","solutions":["Rely on the supervisor's reconnect loop — the next listen() automatically does a fresh Identify and recovers","If it recurs in a tight loop, verify the bot credentials (AppID/secret) and system clock, since a fresh Identify that also fails points to auth problems","Make sure only one process consumes a given QQ bot's WebSocket connection"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"loop {\n    if let Err(err) = channel.listen(&tx).await {\n        if format!(\"{err:#}\").contains(\"invalid session\") {\n            // session state already cleared; next listen() does fresh Identify\n            tokio::time::sleep(backoff.next()).await;\n            continue;\n        }\n        return Err(err);\n    }\n}","preventionTips":["Always route listen() through a supervisor with reconnect + backoff — this error is designed to be retried","Run exactly one listener per QQ bot credential to avoid mutual session invalidation","Expect event loss for the invalid-session window; critical events should be idempotent or re-fetchable"],"tags":["qq","websocket","invalid-session","reconnect","gateway","channel"],"backgroundTag":"websocket-invalid-session","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}