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
- Confirm outbound WSS access to the Mercury host from the deployment environment (firewall/proxy)
- Re-validate the bot token — 401 at upgrade means the token is invalid/expired
- Inspect the redacted error text for the exact transport failure (x509, no route, handshake)
- 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
- Verify outbound TCP 443 / WSS egress in firewalled and containerized environments before deployment
- Set HTTPS_PROXY correctly behind corporate proxies
- Re-validate tokens on upgrade failures (401 at handshake)
- Keep TLS roots updated in minimal container images
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
- read websocket: %w
- wecom-ws: ack timeout
- read register_ack: %w
- cloud_web: websocket disconnected
- qq: ws connect failed (%s): %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/c5b907f45331b713.
Report an issue: GitHub.