chenhg5/cc-connect · error

dial websocket: %s

Error message

dial websocket: %s

What it means

After device registration, runConnection dials the Mercury WebSocket URL. A failed DialContext (with the bot's Bearer token in headers) yields 'dial websocket: %s', with the bot token redacted from the error text. The real-time event stream could not be established.

Source

Thrown at platform/webex/webex.go:321

func (p *Platform) runConnection(ctx context.Context) error {
	dev, err := p.client.CreateDevice(ctx)
	if err != nil {
		return fmt.Errorf("create device: %w", err)
	}
	p.mu.Lock()
	prevDevice := p.deviceURL
	p.deviceURL = dev.URL
	p.mu.Unlock()
	if prevDevice != "" && prevDevice != dev.URL {
		if err := p.client.DeleteDevice(ctx, prevDevice); err != nil {
			slog.Debug("webex: delete stale device failed", "error", err)
		}
	}

	header := map[string][]string{"Authorization": {"Bearer " + p.token}}
	conn, _, err := websocket.DefaultDialer.DialContext(ctx, dev.WebSocketURL, header)
	if err != nil {
		return fmt.Errorf("dial websocket: %s", core.RedactToken(err.Error(), p.token))
	}
	defer func() { _ = conn.Close() }()

	slog.Info("webex: websocket connected")
	if h := p.lifecycle(); h != nil {
		h.OnPlatformReady(p)
	}

	connClosed := make(chan struct{})
	defer close(connClosed)
	go func() {
		select {
		case <-ctx.Done():
			_ = conn.Close()
		case <-connClosed:
		}
	}()

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Confirm outbound WSS access to the Mercury host from the deployment environment (firewall/proxy)
  2. Re-validate the bot token — 401 at upgrade means the token is invalid/expired
  3. Inspect the redacted error text for the exact transport failure (x509, no route, handshake)
  4. Check proxy environment variables (HTTPS_PROXY) are set correctly if behind a corporate proxy

Example fix

// before
// running in container without egress to wss://
// after
// open firewall: outbound TCP 443 to webexapis.com and Mercury WSS hosts
// or configure HTTPS_PROXY for the daemon environment
Defensive patterns

Strategy: retry

Validate before calling

host := "mercury-connection.webexapis.com"
conn, err := net.DialTimeout("tcp", host+":443", 5*time.Second)
if err != nil { log.Fatal("no egress to webex websocket") }

Try / catch

conn, _, err := dialer.DialContext(ctx, wsURL, header)
if err != nil {
    if ctx.Err() == nil { slog.Warn("ws dial failed, retrying", "err", err) }
    return retryable{err}
}

Prevention

When it happens

Trigger: DialContext fails: network unreachable, TLS error, non-101 upgrade response, invalid/expired token rejected at upgrade, or DNS failure for the Mercury host.

Common situations: Corporate firewalls/proxies blocking outbound WSS; token revoked while running; transient network drop during reconnect; expired certificate or MITM proxy interfering with TLS.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/c5b907f45331b713. Report an issue: GitHub.