jaegertracing/jaeger · error

websocket dial %s: %w

Error message

websocket dial %s: %w

What it means

DialWsAdapter establishes the WebSocket connection to an AI agent; when the dialer returns an error (or the upgrade response has a non-101 status) it wraps it as "websocket dial <url>: <cause>". When an HTTP error response is received it also logs status and body at the point of failure for diagnosis.

Source

Thrown at cmd/jaeger/internal/extension/jaegerquery/internal/jaegerai/ws_adapter.go:72

	var header http.Header
	if len(headers) > 0 {
		header = make(http.Header, len(headers))
		for name, value := range headers.Iter {
			header.Set(name, string(value))
		}
	}
	conn, resp, err := dialer.DialContext(ctx, url, header) //nolint:bodyclose // gorilla wraps resp.Body in io.NopCloser; no close needed
	if err != nil {
		if resp != nil {
			body, _ := io.ReadAll(resp.Body)
			logger.Error(
				"WebSocket dial failed",
				zap.Int("status", resp.StatusCode),
				zap.String("body", string(body)),
				zap.Error(err),
			)
		}
		return nil, fmt.Errorf("websocket dial %s: %w", url, err)
	}
	return &WsReadWriteCloser{conn: conn, logger: logger}, nil
}

func (w *WsReadWriteCloser) Read(p []byte) (int, error) {
	if len(p) == 0 {
		return 0, nil
	}
	for {
		if w.r == nil {
			// If the last seen message lacked a delimiter, return the newline
			// before opening the next reader.
			if w.messageUnterminated {
				w.messageUnterminated = false
				p[0] = '\n'
				return 1, nil
			}
			_, r, err := w.conn.NextReader()

View on GitHub (pinned to 806f444784)

Solutions

  1. Read the wrapped cause and the 'WebSocket dial failed' log (status + body) to identify whether it is DNS, connection refused, TLS, or an HTTP error status.
  2. Correct the agent URL in configuration (scheme/host/port/path).
  3. Supply the required agent_headers for authenticated agents.
  4. Verify network reachability and that proxies allow WebSocket upgrades.
Defensive patterns

Strategy: retry

Validate before calling

u, err := url.Parse(agentURL)
if err != nil || (u.Scheme != "ws" && u.Scheme != "wss") || u.Host == "" {
    return fmt.Errorf("bad agent URL: %q", agentURL)
}
for _, h := range agentHeaders {
    if strings.TrimSpace(h.Name) == "" { return fmt.Errorf("blank header name") }
}

Try / catch

conn, err := jaegerai.DialWsAdapter(ctx, url, headers, logger)
if err != nil {
    var httpErr interface{ StatusCode() int } // inspect logged status/body for 401/403 vs network errors
    if strings.Contains(err.Error(), "websocket dial") {
        logger.Error("agent websocket unreachable", zap.Error(err))
    }
    return err
}

Prevention

When it happens

Trigger: Any call path (AI request handling, health checks) where the agent endpoint is unreachable, DNS fails, TLS fails, or the server answers the upgrade with 4xx/5xx instead of switching protocols.

Common situations: Agent service down or misconfigured URL; auth headers missing so the agent returns 401 on upgrade; corporate proxy blocking WebSocket upgrades; wss:// to a host with an invalid certificate.

Related errors


AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01). Data as JSON: /api/errors/736f62f2663172b1. Report an issue: GitHub.