{"record":{"id":"bbe660d52221d13f","repo":"grafana/k6","slug":"couldn-t-parse-cloud-logs-host-w","errorCode":null,"errorMessage":"couldn't parse cloud logs host %w","messagePattern":"couldn't parse cloud logs host %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cloudapi/logs.go","lineNumber":91,"sourceCode":"\t}\n\n\treturn ts\n}\n\nfunc labelsToLogrusFields(labels map[string]string) logrus.Fields {\n\tfields := make(logrus.Fields, len(labels))\n\n\tfor key, val := range labels {\n\t\tfields[key] = val\n\t}\n\n\treturn fields\n}\n\nfunc (c *Config) logtailConn(ctx context.Context, referenceID string, since time.Time) (*websocket.Conn, error) {\n\tu, err := url.Parse(c.LogsTailURL.String)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"couldn't parse cloud logs host %w\", err)\n\t}\n\n\tu.RawQuery = fmt.Sprintf(`query={test_run_id=\"%s\"}&start=%d`, referenceID, since.UnixNano())\n\n\theaders := make(http.Header)\n\theaders.Add(\"Authorization\", \"token \"+c.Token.String)\n\theaders.Add(\"X-K6testrun-Id\", referenceID)\n\n\tvar conn *websocket.Conn\n\terr = retry(sleeperFunc(time.Sleep), 3, 5*time.Second, 2*time.Minute, func() (err error) {\n\t\t// We don't need to close the http body or use it for anything until we want to actually log\n\t\t// what the server returned as body when it errors out\n\t\tconn, _, err = websocket.DefaultDialer.DialContext(ctx, u.String(), headers) //nolint:bodyclose\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/cloudapi/logs.go#L73-L109","documentation":"Cloud log tailing (cloudapi/logs.go) opens a websocket to Config.LogsTailURL - default wss://cloudlogs.k6.io/api/v1/tail, overridable via K6_CLOUD_LOGS_TAIL_URL (cloudapi/config.go:30). Before connecting it url.Parse's the value; a parse failure aborts log tailing with this wrapped error. url.Parse only fails on structurally invalid URLs, so this almost always means a bad override value rather than a transient problem.","triggerScenarios":"K6_CLOUD_LOGS_TAIL_URL containing spaces or control characters, a missing scheme ('cloudlogs.k6.io/api/v1/tail'), or an invalid port ('wss://host:port'); a CI template or orchestrator injecting a mangled value.","commonSituations":"Custom log-tail endpoints for self-hosted setups specified without the ws:// or wss:// scheme; trailing whitespace or quotes around the env value; YAML/CI interpolation artifacts.","solutions":["Unset K6_CLOUD_LOGS_TAIL_URL to fall back to the default wss://cloudlogs.k6.io/api/v1/tail","Fix the value to a full absolute URL with scheme and, if non-default, port: wss://host:8443/api/v1/tail","Strip whitespace/quotes from the env value in CI before launching k6"],"exampleFix":"# before\nexport K6_CLOUD_LOGS_TAIL_URL='cloudlogs.internal:443/api/v1/tail'   # no scheme\n\n# after\nexport K6_CLOUD_LOGS_TAIL_URL='wss://cloudlogs.internal:443/api/v1/tail'","handlingStrategy":"validation","validationCode":"# reject malformed log-tail URLs before the run\npython3 - <<'EOF'\nfrom urllib.parse import urlparse\nimport os, sys\nu = os.environ.get('K6_CLOUD_LOGS_TAIL_URL', 'wss://cloudlogs.k6.io/api/v1/tail')\np = urlparse(u)\nassert p.scheme in ('ws', 'wss') and p.hostname, f'bad K6_CLOUD_LOGS_TAIL_URL: {u!r}'\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always include the ws:// or wss:// scheme in K6_CLOUD_LOGS_TAIL_URL overrides","Lint CI env files for values with spaces, quotes, or missing schemes","Prefer unsetting the variable to inherit the tested default endpoint"],"tags":["cloud","configuration","url","websocket","logs"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}