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

  1. Let the supervisor reconnect — Resume usually restores the session without event loss
  2. If it recurs frequently, verify heartbeats are flowing (see the heartbeat path) so intermediaries do not idle the connection out
  3. 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

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

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/174cdf9a3f5b1488. Report an issue: GitHub.