chenhg5/cc-connect · error

weibo: ws send: %w

Error message

weibo: ws send: %w

What it means

writeWS holds wsMu and calls p.ws.WriteJSON(data); when the gorilla/websocket write fails (socket closed, broken pipe, protocol error) the raw error is wrapped as 'weibo: ws send: %w' and propagated to the sendMessage/SendImage/SendFile caller. Unlike 'not connected', this means a WebSocket object existed but the underlying TCP/TLS write failed. Use errors.Unwrap / %T to inspect the root cause (e.g. use of closed network connection).

Source

Thrown at platform/weibo/weibo.go:634

			}},
		},
	}
	slog.Debug(p.tag()+": sending file", "to", rc.fromUserID, "name", fname, "size", len(file.Data))
	return p.writeWS(env)
}

func (p *Platform) writeWS(data any) error {
	// gorilla/websocket only allows one concurrent writer; wsMu must guard the
	// full WriteJSON call (pingLoop already follows this pattern), otherwise
	// concurrent sendMessage / SendImage / SendFile calls interleave frames
	// on the wire.
	p.wsMu.Lock()
	defer p.wsMu.Unlock()
	if p.ws == nil {
		return fmt.Errorf("weibo: not connected")
	}
	if err := p.ws.WriteJSON(data); err != nil {
		return fmt.Errorf("weibo: ws send: %w", err)
	}
	return nil
}

// --- Helpers ---

func (p *Platform) tag() string { return p.name }

func (p *Platform) isDuplicate(msgID string) bool {
	p.seenMu.Lock()
	defer p.seenMu.Unlock()
	if _, ok := p.seen[msgID]; ok {
		return true
	}
	if len(p.seen) >= maxSeenMessages {
		// prune half
		i := 0
		for k := range p.seen {

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the wrapped cause with errors.Unwrap or errors.Is to distinguish closed-connection (needs reconnect) from transient write failure (retry)
  2. Trigger/await reconnect and resend the message
  3. Check ping/pong keepalive settings if disconnects are frequent
  4. Log at warn level and let the engine's message queue retry

Example fix

err := p.writeWS(env)
if err != nil {
    var netErr net.Error
    if errors.As(err, &netErr) || strings.Contains(err.Error(), "closed") {
        p.reconnect() // socket is dead; rebuild p.ws
    }
    return fmt.Errorf("weibo: send failed: %w", err)
}
Defensive patterns

Strategy: retry

Try / catch

if err := p.writeWS(frame); err != nil {
    var werr *websocket.CloseError
    if errors.As(err, &werr) || errors.Is(errors.Unwrap(err), syscall.EPIPE) {
        p.scheduleReconnect()
        return retrySend(frame)
    }
    return err
}

Prevention

When it happens

Trigger: Writing a frame on a socket the peer already closed; server-side disconnect mid-frame; TLS or TCP reset during WriteJSON; write attempted concurrently with close (WriteJSON itself is serialized here by wsMu, but the fd can still be dead).

Common situations: Mobile/unstable networks dropping long-lived WebSockets; Weibo server restarting; NAT/proxy idle timeouts severing the connection between pings.

Related errors


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