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

  1. Read the wrapped cause: websocket.IsCloseError codes, timeouts, or connection reset each point to a different fix
  2. Confirm the heartbeat loop is running and heartbeatPeriod is shorter than any NAT/proxy idle timeout on the path
  3. Check whether the server sent a fatal error frame (e.g. NOT_LOGIN) before closing — re-authenticate or refresh credentials
  4. 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

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


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/3701008ed209252e. Report an issue: GitHub.