chenhg5/cc-connect · error
wps-agentspace: read: %w
Error message
wps-agentspace: read: %w
What it means
readLoop surfaces any WebSocket read error that is not a normal (1000) or going-away (1001) close as "wps-agentspace: read: %w". This terminates connect(), tearing down the session so connectLoop can reconnect with backoff. It indicates the socket broke unexpectedly: timeout (readDeadline), abrupt TCP reset, TLS termination, or an abnormal close code from the server.
Source
Thrown at platform/wps-agentspace/wpsagentspace.go:459
}
}
}
// readLoop processes incoming WebSocket messages.
func (p *Platform) readLoop(conn *websocket.Conn, ctx context.Context) error {
for {
if p.stopped.Load() {
return nil
}
_ = conn.SetReadDeadline(time.Now().Add(readDeadline))
_, raw, err := conn.ReadMessage()
if err != nil {
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
return nil
}
return fmt.Errorf("wps-agentspace: read: %w", err)
}
var frame wsFrame
if err := json.Unmarshal(raw, &frame); err != nil {
slog.Warn("wps-agentspace: invalid frame", "error", err)
continue
}
if err := p.handleFrame(frame); err != nil {
slog.Error("wps-agentspace: handle frame", "error", err)
}
}
}
// handleFrame dispatches incoming frames.
func (p *Platform) handleFrame(frame wsFrame) error {
slog.Debug("wps-agentspace: received frame", "event", frame.Event, "data", string(frame.Data))
View on GitHub (pinned to 4000b2338a)
Solutions
- Read the wrapped cause: websocket.IsCloseError codes, timeouts, or connection reset each point to a different fix
- Confirm the heartbeat loop is running and heartbeatPeriod is shorter than any NAT/proxy idle timeout on the path
- Check whether the server sent a fatal error frame (e.g. NOT_LOGIN) before closing — re-authenticate or refresh credentials
- Treat it as transient: connectLoop retries with backoff; if it recurs immediately, inspect server status and local network
Example fix
// before
if err != nil {
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
return nil
}
return fmt.Errorf("wps-agentspace: read: %w", err)
}
// after
if err != nil {
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
return nil
}
slog.Warn("wps-agentspace: read failed, reconnecting", "error", err)
return fmt.Errorf("wps-agentspace: read: %w", err)
} Defensive patterns
Strategy: retry
Try / catch
if err := p.readLoop(conn, ctx); err != nil {
if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
return nil // clean shutdown, no reconnect needed
}
slog.Warn("read error; reconnecting with backoff", "error", err)
} Prevention
- Set heartbeatPeriod below any NAT/proxy idle timeout on the network path
- Monitor for frequent read errors — indicates server instability or auth expiry
- Make readDeadline exceed the server's ping interval
- Handle fatal error frames (NOT_LOGIN) before they surface as read failures
When it happens
Trigger: No frames arrive within readDeadline (SetReadDeadline at wpsagentspace.go:452) so ReadMessage returns a timeout; the server sends close 1011 (internal error) or an unexpected close code; the TCP connection is reset; heartbeat pings stop being answered and the server kills the link.
Common situations: Idle NAT/firewall dropping the WebSocket because heartbeats were insufficient; AgentSpace server restart or deploy; network interruption on the host; server closing with an abnormal close code due to auth expiry (NOT_LOGIN) mid-session.
Related errors
- wecom-ws: ack timeout
- read register_ack: %w
- upload init: %w
- upload chunk %d: %w
- wps-agentspace: dial: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/3701008ed209252e.
Report an issue: GitHub.