{"record":{"id":"0b6c4aeb2fb00a89","repo":"chenhg5/cc-connect","slug":"weibo-ws-send-w","errorCode":null,"errorMessage":"weibo: ws send: %w","messagePattern":"weibo: ws send: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/weibo/weibo.go","lineNumber":634,"sourceCode":"\t\t\t}},\n\t\t},\n\t}\n\tslog.Debug(p.tag()+\": sending file\", \"to\", rc.fromUserID, \"name\", fname, \"size\", len(file.Data))\n\treturn p.writeWS(env)\n}\n\nfunc (p *Platform) writeWS(data any) error {\n\t// gorilla/websocket only allows one concurrent writer; wsMu must guard the\n\t// full WriteJSON call (pingLoop already follows this pattern), otherwise\n\t// concurrent sendMessage / SendImage / SendFile calls interleave frames\n\t// on the wire.\n\tp.wsMu.Lock()\n\tdefer p.wsMu.Unlock()\n\tif p.ws == nil {\n\t\treturn fmt.Errorf(\"weibo: not connected\")\n\t}\n\tif err := p.ws.WriteJSON(data); err != nil {\n\t\treturn fmt.Errorf(\"weibo: ws send: %w\", err)\n\t}\n\treturn nil\n}\n\n// --- Helpers ---\n\nfunc (p *Platform) tag() string { return p.name }\n\nfunc (p *Platform) isDuplicate(msgID string) bool {\n\tp.seenMu.Lock()\n\tdefer p.seenMu.Unlock()\n\tif _, ok := p.seen[msgID]; ok {\n\t\treturn true\n\t}\n\tif len(p.seen) >= maxSeenMessages {\n\t\t// prune half\n\t\ti := 0\n\t\tfor k := range p.seen {","sourceCodeStart":616,"sourceCodeEnd":652,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/weibo/weibo.go#L616-L652","documentation":"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).","triggerScenarios":"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).","commonSituations":"Mobile/unstable networks dropping long-lived WebSockets; Weibo server restarting; NAT/proxy idle timeouts severing the connection between pings.","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"],"exampleFix":"err := p.writeWS(env)\nif err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) || strings.Contains(err.Error(), \"closed\") {\n        p.reconnect() // socket is dead; rebuild p.ws\n    }\n    return fmt.Errorf(\"weibo: send failed: %w\", err)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := p.writeWS(frame); err != nil {\n    var werr *websocket.CloseError\n    if errors.As(err, &werr) || errors.Is(errors.Unwrap(err), syscall.EPIPE) {\n        p.scheduleReconnect()\n        return retrySend(frame)\n    }\n    return err\n}","preventionTips":["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"],"tags":["weibo","websocket","network","write-failed"],"backgroundTag":"broken-pipe","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}