{"record":{"id":"677b9f2fca780860","repo":"sipeed/picoclaw","slug":"whatsapp-send-w","errorCode":null,"errorMessage":"whatsapp send: %w","messagePattern":"whatsapp send: %w","errorType":"exception","errorClass":"channels.ErrTemporary","httpStatus":null,"severity":"error","filePath":"pkg/channels/whatsapp/whatsapp.go","lineNumber":144,"sourceCode":"\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}\n\t_ = c.conn.SetWriteDeadline(time.Time{})\n\n\treturn nil, nil\n}\n\nfunc (c *WhatsAppChannel) listen() {\n\tfor {\n\t\tselect {\n\t\tcase <-c.ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t\tc.mu.Lock()\n\t\t\tconn := c.conn\n\t\t\tc.mu.Unlock()\n\n\t\t\tif conn == nil {\n\t\t\t\ttime.Sleep(1 * time.Second)","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/whatsapp/whatsapp.go#L126-L162","documentation":"Returned by the WhatsApp bridge channel's Send when conn.WriteMessage fails within a 10-second write deadline. The write deadline is cleared and the raw websocket error is discarded in favour of the channels.ErrTemporary sentinel, so the manager retries the message with exponential backoff. Typical websocket causes: connection already closed by peer, TCP reset, or the peer stalling until the deadline expires.","triggerScenarios":"Send() writes a TextMessage on a websocket whose remote end has disconnected (bridge crash/restart), whose TCP link is broken, or whose read buffer backpressure stalls past the 10s SetWriteDeadline.","commonSituations":"Bridge restarted under load; NAT/firewall silently dropping the idle websocket (no keepalive/ping configured); mobile/network flaps on the host running the bridge; slow bridge consumer with full receive buffers.","solutions":["Verify the bridge process is alive and its logs show no crash/restart loop.","If a NAT/firewall idles out the socket, enable websocket ping/keepalive on the bridge or dialer so drops are detected proactively.","Restart the picoclaw channel (Stop/Start) to re-dial the bridge - the manager's backoff will then re-deliver the queued message.","Check host networking (DNS, egress) if writes fail immediately after dial."],"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        // write failed or conn lost: backoff and retry; consider Stop/Start of channel if repeated\n    }\n}","preventionTips":["Enable websocket keepalive/ping on the bridge path so dead peers are detected before writes.","Alert on repeated ErrTemporary from the whatsapp channel - it usually means the bridge is flapping.","Restart the channel via Stop/Start when errors persist to force a clean re-dial."],"tags":["whatsapp","websocket","write-timeout","transient"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}