jaegertracing/jaeger · error

websocket dial

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.

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.

Appendix: 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)