zeroclaw-labs/zeroclaw · error
QQ WebSocket connection closed: close_code={code}, reason="{
Error message
QQ WebSocket connection closed: close_code={code}, reason="{reason}" What it means
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.
Source
Thrown at crates/zeroclaw-channels/src/qq.rs:1789
*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}\""
)
}
ExitReason::StreamEnded => {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
.with_outcome(::zeroclaw_log::EventOutcome::Unknown),
"WebSocket stream ended unexpectedly; resume will be attempted on reconnect"
);
anyhow::bail!("QQ WebSocket connection closed: stream ended unexpectedly")
}
ExitReason::HeartbeatTimeout => {
::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!({"MAX_MISSED_ACKS": MAX_MISSED_ACKS})),View on GitHub (pinned to 88bb9c8533)
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
Defensive patterns
Strategy: try-catch
Try / catch
if let Err(err) = channel.listen(&tx).await {
let msg = format!("{err:#}");
if msg.contains("close_code=4004") || msg.contains("close_code=4010") {
return Err(err); // auth/shard close codes: stop reconnecting until credentials are fixed
}
tokio::time::sleep(backoff.next()).await;
continue; // other codes: reconnect and Resume
} Prevention
- 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
When it happens
Trigger: 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'.
Common situations: 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.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- QQ WebSocket connection closed: invalid session (fresh auth
- 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/2baf8d8ad9104d59.
Report an issue: GitHub.