{"record":{"id":"c10f1cdf9cd8376c","repo":"chenhg5/cc-connect","slug":"cloud-web-websocket-not-connected","errorCode":null,"errorMessage":"cloud_web: websocket not connected","messagePattern":"cloud_web: websocket not connected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/cloud-web/ws.go","lineNumber":272,"sourceCode":"\t\tif base.Type == \"capabilities_changed\" {\n\t\t\tvar ch wireCapabilitiesChanged\n\t\t\tif err := json.Unmarshal(raw, &ch); err == nil && len(ch.Capabilities) > 0 {\n\t\t\t\tt.setCaps(capabilitySet(ch.Capabilities))\n\t\t\t}\n\t\t}\n\tdefault:\n\t\tif t.onInbound != nil {\n\t\t\tt.onInbound(raw)\n\t\t}\n\t}\n}\n\nfunc (t *wsTransport) Send(ctx context.Context, msg map[string]any) error {\n\tt.mu.RLock()\n\tconn := t.conn\n\tt.mu.RUnlock()\n\tif conn == nil {\n\t\treturn fmt.Errorf(\"cloud_web: websocket not connected\")\n\t}\n\tt.writeMu.Lock()\n\tdefer t.writeMu.Unlock()\n\treturn conn.WriteJSON(msg)\n}\n\nfunc (t *wsTransport) waitPreviewAck(refID string, timeout time.Duration) (string, error) {\n\tch := make(chan string, 1)\n\tt.previewMu.Lock()\n\tt.previewRequests[refID] = ch\n\tt.previewMu.Unlock()\n\tdefer func() {\n\t\tt.previewMu.Lock()\n\t\tdelete(t.previewRequests, refID)\n\t\tt.previewMu.Unlock()\n\t}()\n\tselect {\n\tcase handle := <-ch:","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/cloud-web/ws.go#L254-L290","documentation":"wsTransport.Send checks the current connection under a read lock; if t.conn is nil (no active WebSocket), it returns this error instead of attempting a write. Callers sending a message through the cloud-web transport while disconnected get this immediate failure rather than a blocked write.","triggerScenarios":"Send(ctx, msg) is invoked before connectLoop has established a connection, after a disconnect before reconnection completes, or during shutdown after the connection was cleared.","commonSituations":"Race between a message reply (e.g. streaming card update or preview) and a network drop; engine starts sending before the WebSocket handshake finishes; server outage with queued outbound messages; Stop() already called.","solutions":["Retry after the transport reconnects — check connection state / wait for onConnected before sending","Buffer or queue outbound messages while disconnected and flush on reconnect","Check why the connection is down (see disconnect logs) and restore connectivity/server","Avoid calling Send before Start/connect completes; gate sends on a connected flag"],"exampleFix":"// before\nif err := t.Send(ctx, msg); err != nil { ... }\n// after (retry with backoff while disconnected)\nif err := t.Send(ctx, msg); err != nil && strings.Contains(err.Error(), \"not connected\") {\n    time.Sleep(retryDelay)\n    err = t.Send(ctx, msg)\n}","handlingStrategy":"retry","validationCode":"// Guard before sending\nfunc (t *wsTransport) CanSend() bool {\n    t.mu.RLock()\n    defer t.mu.RUnlock()\n    return t.conn != nil\n}","typeGuard":null,"tryCatchPattern":"// Retry send after reconnect\nif err := t.Send(ctx, msg); err != nil {\n    if strings.Contains(err.Error(), \"not connected\") {\n        <-t.Connected() // channel closed on reconnect\n        err = t.Send(ctx, msg)\n    }\n}","preventionTips":["Gate sends on connection-established signal (onConnected)","Queue outbound messages during outages and flush on reconnect","Don't call Send between Stop() and the next Start()","Alert on 'not connected' rates to detect chronic disconnections"],"tags":["websocket","disconnected","send","cloud-web"],"backgroundTag":"connection-refused","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"}