sipeed/picoclaw · critical

failed to connect to WhatsApp bridge: %w

Error message

failed to connect to WhatsApp bridge: %w

What it means

Returned by WhatsAppChannel.Start when gorilla/websocket's Dialer.Dial fails to establish a connection to the configured bridge URL (cfg.BridgeURL). The %w chain preserves the underlying error, commonly websocket.ErrBadHandshake when the server replies with a non-101 status. The channel's internal context is cancelled before returning, so the channel is left stopped.

Source

Thrown at pkg/channels/whatsapp/whatsapp.go:69

}

func (c *WhatsAppChannel) Start(ctx context.Context) error {
	logger.InfoCF("whatsapp", "Starting WhatsApp channel", map[string]any{
		"bridge_url": c.url,
	})

	c.ctx, c.cancel = context.WithCancel(ctx)

	dialer := websocket.DefaultDialer
	dialer.HandshakeTimeout = 10 * time.Second

	conn, resp, err := dialer.Dial(c.url, nil)
	if resp != nil {
		_ = resp.Body.Close()
	}
	if err != nil {
		c.cancel()
		return fmt.Errorf("failed to connect to WhatsApp bridge: %w", err)
	}

	c.mu.Lock()
	c.conn = conn
	c.connected = true
	c.mu.Unlock()

	c.SetRunning(true)
	logger.InfoC("whatsapp", "WhatsApp channel connected")

	go c.listen()

	return nil
}

func (c *WhatsAppChannel) Stop(ctx context.Context) error {
	logger.InfoC("whatsapp", "Stopping WhatsApp channel...")

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Verify the bridge process is running and its port matches config.BridgeURL (e.g. ws://127.0.0.1:PORT).
  2. Test reachability with a standalone websocket client or curl against the bridge's HTTP health endpoint.
  3. If a reverse proxy fronts the bridge, add websocket upgrade headers (Upgrade/Connection) to that route.
  4. For wss:// certificate failures, fix the cert chain or point to a properly signed cert.
  5. Ensure the URL scheme is ws:// or wss://, not http:// or https://.

Example fix

// before
url := "http://127.0.0.1:8125"  // wrong scheme
// after
url := "ws://127.0.0.1:8125"
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(cfg.BridgeURL)
if err != nil || (u.Scheme != "ws" && u.Scheme != "wss") {
    return fmt.Errorf("bridge URL must be ws:// or wss://, got %q", cfg.BridgeURL)
}
if _, err := net.DialTimeout("tcp", u.Host, 3*time.Second); err != nil {
    return fmt.Errorf("bridge unreachable: %w", err)
}

Try / catch

if err := ch.Start(ctx); err != nil {
    if strings.Contains(err.Error(), "failed to connect to WhatsApp bridge") {
        // inspect errors.Unwrap chain for websocket.ErrBadHandshake => HTTP-level problem
    }
    return err
}

Prevention

When it happens

Trigger: Start(ctx) calls dialer.Dial(c.url, nil) with a 10s handshake timeout: bridge process not running, wrong scheme (http:// instead of ws://), bridge listening on a different host/port, TLS failure on wss://, or reverse proxy not upgrading the connection.

Common situations: The WhatsApp bridge container/service (e.g. a whatsapp-web bridge) is down or was never started; BridgeURL in channel config points to the wrong port; an nginx/caddy front proxy lacks Upgrade/Connection headers for websocket; self-signed cert on wss:// rejected by default TLS config.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/e61b27f154d69f63. Report an issue: GitHub.