zeroclaw-labs/zeroclaw · error
QQ WebSocket connection closed: invalid session (fresh auth
Error message
QQ WebSocket connection closed: invalid session (fresh auth required)
What it means
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.
Source
Thrown at crates/zeroclaw-channels/src/qq.rs:1773
exit_reason = ExitReason::ChannelClosed;
break 'outer;
}
}
_ => {}
}
}
}
}
// Persist sequence number for potential resume on next reconnect
*self.last_sequence.write().await = if sequence >= 0 { Some(sequence) } else { None };
match exit_reason {
ExitReason::InvalidSession => {
// Clear stored session so next reconnect does a fresh Identify
*self.session_id.write().await = None;
*self.last_sequence.write().await = None;
anyhow::bail!(
"QQ WebSocket connection closed: invalid session (fresh auth required)"
)
}
ExitReason::Reconnect => {
// Session state preserved — supervisor will reconnect and we'll attempt Resume
anyhow::bail!(
"QQ WebSocket connection closed: server requested reconnect (resume will be attempted)"
)
}
ExitReason::Close(ref frame) => {
let (code, reason) = frame
.as_ref()
.map(|f| (f.code.to_string(), f.reason.to_string()))
.unwrap_or_else(|| ("unknown".into(), "none".into()));
::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");
anyhow::bail!(
"QQ WebSocket connection closed: close_code={code}, reason=\"{reason}\""
)View on GitHub (pinned to 88bb9c8533)
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
Defensive patterns
Strategy: retry
Try / catch
loop {
if let Err(err) = channel.listen(&tx).await {
if format!("{err:#}").contains("invalid session") {
// session state already cleared; next listen() does fresh Identify
tokio::time::sleep(backoff.next()).await;
continue;
}
return Err(err);
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- QQ WebSocket connection closed: close_code={code}, reason="{
- QQ WebSocket connection closed: server requested reconnect (
- QQ gateway request failed ({status}): {err}
- QQ WebSocket connection closed: stream ended unexpectedly
- QQ WebSocket connection closed: heartbeat ACK timeout ({MAX_
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/9b162a093f93cf0a.
Report an issue: GitHub.