{"record":{"id":"73606bbcf94b51aa","repo":"chenhg5/cc-connect","slug":"read-websocket-w","errorCode":null,"errorMessage":"read websocket: %w","messagePattern":"read websocket: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"platform/webex/webex.go","lineNumber":346,"sourceCode":"\t}\n\n\tconnClosed := make(chan struct{})\n\tdefer close(connClosed)\n\tgo func() {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\t_ = conn.Close()\n\t\tcase <-connClosed:\n\t\t}\n\t}()\n\n\tfor {\n\t\t_, data, err := conn.ReadMessage()\n\t\tif err != nil {\n\t\t\tif ctx.Err() != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"read websocket: %w\", err)\n\t\t}\n\t\tp.handleFrame(ctx, data)\n\t}\n}\n\n// handleFrame parses one Mercury WebSocket frame and dispatches qualifying\n// messages. The frame body is encrypted, so we fetch the decrypted message\n// via REST using the activity ID.\nfunc (p *Platform) handleFrame(ctx context.Context, data []byte) {\n\tvar ev wsEvent\n\tif err := json.Unmarshal(data, &ev); err != nil {\n\t\tslog.Debug(\"webex: non-JSON frame\", \"error\", err)\n\t\treturn\n\t}\n\t// \"post\" = text message, \"share\" = file/image upload. Other verbs (e.g.\n\t// \"update\" for malware-scan completion, \"delete\") are re-notifications we\n\t// must ignore to avoid double-processing.\n\tif ev.Data.EventType != \"conversation.activity\" {","sourceCodeStart":328,"sourceCodeEnd":364,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/webex/webex.go#L328-L364","documentation":"The WebSocket read loop returns 'read websocket: %w' when conn.ReadMessage fails while the context is still alive (i.e., not a deliberate shutdown). This indicates the Mercury connection dropped unexpectedly; the outer loop will reconnect.","triggerScenarios":"Network interruption, server-side close/timeout, ping-pong timeout, or proxy idle-connection teardown causing ReadMessage to return an error while ctx is not cancelled.","commonSituations":"Long idle periods with intermediary (NAT/firewall) dropping the connection; Webex server maintenance; mobile/unstable network on the host.","solutions":["Confirm the reconnect loop treats this as transient and redials (expected behavior)","If drops are frequent, verify keep-alive/ping handling and any proxy idle timeouts","Check host network stability (NAT timeout on idle WSS, VPN drops)","Run with debug logs to capture the wrapped net.OpError / close message to distinguish server close vs. transport drop"],"exampleFix":"// before\nif err := runConnection(ctx); err != nil {\n    slog.Error(\"fatal\", \"err\", err) // treat as fatal\n    os.Exit(1)\n}\n// after\nif err := runConnection(ctx); err != nil {\n    if ctx.Err() == nil {\n        slog.Warn(\"webex: websocket dropped, reconnecting\", \"err\", err)\n    }\n    continue // reconnect\n}","handlingStrategy":"retry","validationCode":"// ensure a supervised reconnect loop exists around the read loop\n// and that idle keep-alives (ping) are enabled","typeGuard":null,"tryCatchPattern":"for {\n    _, data, err := conn.ReadMessage()\n    if err != nil {\n        if ctx.Err() != nil { return nil }\n        slog.Warn(\"ws dropped, reconnecting\", \"err\", err)\n        return reconnectNeeded{err}\n    }\n    handleFrame(ctx, data)\n}","preventionTips":["Always treat read errors as transient unless the context is cancelled","Enable/verify WebSocket ping-pong keep-alives to survive NAT idle timeouts","Deploy with a supervisor loop that redials after drops","Log the wrapped error type to spot recurring infrastructure causes"],"tags":["webex","websocket","network","reconnect"],"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"}