{"record":{"id":"736f62f2663172b1","repo":"jaegertracing/jaeger","slug":"websocket-dial-s-w","errorCode":null,"errorMessage":"websocket dial %s: %w","messagePattern":"websocket dial (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/jaeger/internal/extension/jaegerquery/internal/jaegerai/ws_adapter.go","lineNumber":72,"sourceCode":"\tvar header http.Header\n\tif len(headers) > 0 {\n\t\theader = make(http.Header, len(headers))\n\t\tfor name, value := range headers.Iter {\n\t\t\theader.Set(name, string(value))\n\t\t}\n\t}\n\tconn, resp, err := dialer.DialContext(ctx, url, header) //nolint:bodyclose // gorilla wraps resp.Body in io.NopCloser; no close needed\n\tif err != nil {\n\t\tif resp != nil {\n\t\t\tbody, _ := io.ReadAll(resp.Body)\n\t\t\tlogger.Error(\n\t\t\t\t\"WebSocket dial failed\",\n\t\t\t\tzap.Int(\"status\", resp.StatusCode),\n\t\t\t\tzap.String(\"body\", string(body)),\n\t\t\t\tzap.Error(err),\n\t\t\t)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"websocket dial %s: %w\", url, err)\n\t}\n\treturn &WsReadWriteCloser{conn: conn, logger: logger}, nil\n}\n\nfunc (w *WsReadWriteCloser) Read(p []byte) (int, error) {\n\tif len(p) == 0 {\n\t\treturn 0, nil\n\t}\n\tfor {\n\t\tif w.r == nil {\n\t\t\t// If the last seen message lacked a delimiter, return the newline\n\t\t\t// before opening the next reader.\n\t\t\tif w.messageUnterminated {\n\t\t\t\tw.messageUnterminated = false\n\t\t\t\tp[0] = '\\n'\n\t\t\t\treturn 1, nil\n\t\t\t}\n\t\t\t_, r, err := w.conn.NextReader()","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/jaegertracing/jaeger/blob/806f4447841ecdb60519f408b004a599d515f437/cmd/jaeger/internal/extension/jaegerquery/internal/jaegerai/ws_adapter.go#L54-L90","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Correct the agent URL in configuration (scheme/host/port/path).","Supply the required agent_headers for authenticated agents.","Verify network reachability and that proxies allow WebSocket upgrades."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"u, err := url.Parse(agentURL)\nif err != nil || (u.Scheme != \"ws\" && u.Scheme != \"wss\") || u.Host == \"\" {\n    return fmt.Errorf(\"bad agent URL: %q\", agentURL)\n}\nfor _, h := range agentHeaders {\n    if strings.TrimSpace(h.Name) == \"\" { return fmt.Errorf(\"blank header name\") }\n}","typeGuard":null,"tryCatchPattern":"conn, err := jaegerai.DialWsAdapter(ctx, url, headers, logger)\nif err != nil {\n    var httpErr interface{ StatusCode() int } // inspect logged status/body for 401/403 vs network errors\n    if strings.Contains(err.Error(), \"websocket dial\") {\n        logger.Error(\"agent websocket unreachable\", zap.Error(err))\n    }\n    return err\n}","preventionTips":["Pre-flight check agent reachability (TCP/HTTP) before dialing.","Configure auth headers (e.g. Authorization) for agents that require them.","Use wss:// with valid certificates; test through any corporate proxy.","Apply exponential backoff on dial retries to avoid hammering a down agent."],"tags":["websocket","network","connection"],"backgroundTag":"websocket-dial-failed","analyzedSha":"806f4447841ecdb60519f408b004a599d515f437","analyzedAt":"2026-09-01T02:39:22.140Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}