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
- Inspect the wrapped cause with errors.Unwrap or errors.Is to distinguish closed-connection (needs reconnect) from transient write failure (retry)
- Trigger/await reconnect and resend the message
- Check ping/pong keepalive settings if disconnects are frequent
- 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
- Enable websocket ping/pong keepalives to detect dead connections early
- Reconnect on any write error — the socket is likely unusable afterward
- Wrap send failures with %w so callers can errors.As the root cause
- Persist unsent messages to a queue for redelivery after reconnect
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
- wecom-ws: ack timeout
- read register_ack: %w
- cloud_web: websocket disconnected
- qq: ws connect failed (%s): %w
- qq: ws write: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/0b6c4aeb2fb00a89.
Report an issue: GitHub.