zeroclaw-labs/zeroclaw · error
QQ WebSocket connection closed: stream ended unexpectedly
Error message
QQ WebSocket connection closed: stream ended unexpectedly
What it means
Raised by QQChannel::listen when the WebSocket stream terminates without a Close frame (read.next() returned None, ExitReason::StreamEnded). This means the TCP/TLS connection dropped abruptly — the server or an intermediary vanished rather than closing politely — and a WARN is logged stating resume will be attempted. Session state is preserved, so the next listen() tries Resume.
Source
Thrown at crates/zeroclaw-channels/src/qq.rs:1800
}
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})),
"heartbeat timeout after consecutive missed ACKs; resume will be attempted on reconnect"
);
anyhow::bail!(
"QQ WebSocket connection closed: heartbeat ACK timeout \
({MAX_MISSED_ACKS} consecutive missed ACKs)"
)
}
ExitReason::WriteFailed => {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)View on GitHub (pinned to 88bb9c8533)
Solutions
- Let the supervisor reconnect — Resume usually restores the session without event loss
- If it recurs frequently, verify heartbeats are flowing (see the heartbeat path) so intermediaries do not idle the connection out
- Check network stability between the host and the QQ gateway (proxy, NAT, firewall timeouts)
Defensive patterns
Strategy: retry
Try / catch
if let Err(err) = channel.listen(&tx).await {
if format!("{err:#}").contains("stream ended unexpectedly") {
tokio::time::sleep(backoff.next()).await;
continue; // session preserved; Resume on reconnect
}
return Err(err);
} Prevention
- Heartbeats keep the connection alive — make sure the heartbeat interval negotiated with the gateway is respected
- Avoid NAT/proxy idle timeouts shorter than the heartbeat interval
- Handle process suspend/resume by tearing the listener down cleanly instead of letting the stream die mid-read
When it happens
Trigger: The select arm 'msg = read.next()' yields None during the gateway loop: connection reset by the server, NAT/idle timeout, VPN or proxy dropping the socket, or the process's network interface going away.
Common situations: Bots running behind NAT gateways or corporate proxies with aggressive idle timeouts (heartbeats should normally prevent this); flaky container networking; QQ gateway restarts that kill sockets without a close frame; laptops suspending mid-session.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- QQ WebSocket connection closed: server requested reconnect (
- QQ WebSocket connection closed: heartbeat ACK timeout ({MAX_
- QQ WebSocket connection closed: write failed
- QQ gateway request failed ({status}): {err}
- QQ WebSocket connection closed: invalid session (fresh auth
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/174cdf9a3f5b1488.
Report an issue: GitHub.