{"record":{"id":"1af0ca97c83f7215","repo":"sipeed/picoclaw","slug":"whatsapp-connection-not-established-w","errorCode":null,"errorMessage":"whatsapp connection not established: %w","messagePattern":"whatsapp connection not established: %w","errorType":"exception","errorClass":"channels.ErrTemporary","httpStatus":null,"severity":"warning","filePath":"pkg/channels/whatsapp/whatsapp.go","lineNumber":127,"sourceCode":"}\n\nfunc (c *WhatsAppChannel) Send(ctx context.Context, msg bus.OutboundMessage) ([]string, error) {\n\tif !c.IsRunning() {\n\t\treturn nil, channels.ErrNotRunning\n\t}\n\n\t// Check ctx before acquiring lock\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\tdefault:\n\t}\n\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\n\tif c.conn == nil {\n\t\treturn nil, fmt.Errorf(\"whatsapp connection not established: %w\", channels.ErrTemporary)\n\t}\n\n\tpayload := map[string]any{\n\t\t\"type\":    \"message\",\n\t\t\"to\":      msg.ChatID,\n\t\t\"content\": msg.Content,\n\t}\n\n\tdata, err := json.Marshal(payload)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to marshal message: %w\", err)\n\t}\n\n\t_ = c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))\n\tif err := c.conn.WriteMessage(websocket.TextMessage, data); err != nil {\n\t\t_ = c.conn.SetWriteDeadline(time.Time{})\n\t\treturn nil, fmt.Errorf(\"whatsapp send: %w\", channels.ErrTemporary)\n\t}","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/whatsapp/whatsapp.go#L109-L145","documentation":"Returned by the WhatsApp bridge channel's Send when c.conn is nil - i.e. Start() never completed a bridge connection or Stop() has torn it down. It wraps channels.ErrTemporary, so the channel manager treats it as transient and retries with backoff rather than dropping the message permanently.","triggerScenarios":"Send(ctx, msg) is invoked after Start() failed (bridge unreachable), after Stop() released the websocket, or while the channel is between a connection drop and restart.","commonSituations":"Bridge outage that crashed Start; sending queued messages right after process restart before the channel reconnects; a Stop/Start race during reconfiguration where Send slips in with conn still nil.","solutions":["Check whether the channel reports running state before send - IsRunning() gate usually prevents this path.","Fix the underlying bridge connectivity so Start() succeeds (see bridge URL, process, proxy checks).","Rely on the manager's ErrTemporary backoff: queued outbound messages retry automatically once the bridge is back.","If it persists, inspect logs for a prior 'failed to connect to WhatsApp bridge' error explaining why conn is nil."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if !ch.IsRunning() {\n    return errors.New(\"whatsapp bridge channel not running\")\n}","typeGuard":"func isTemporary(err error) bool { return errors.Is(err, channels.ErrTemporary) }","tryCatchPattern":"if _, err := ch.Send(ctx, msg); err != nil {\n    if errors.Is(err, channels.ErrTemporary) {\n        // conn nil or send failure: retry after bridge reconnects\n    }\n}","preventionTips":["Gate sends on IsRunning() to avoid sending into a channel whose Start failed.","Keep the bridge process supervised so conn is re-established quickly.","Let the channel manager own retry policy instead of ad-hoc retries in callers."],"tags":["whatsapp","websocket","send","transient"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}