{"record":{"id":"69d92a095242b470","repo":"grafana/k6","slug":"unmarshaling-hosts-option-w","errorCode":null,"errorMessage":"unmarshaling hosts option: %w","messagePattern":"unmarshaling hosts option: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/js/modules/k6/browser/chromium/browser_type.go","lineNumber":537,"sourceCode":"\n\t// Add the host resolver rules.\n\t//\n\t// This is done by marshaling the k6 hosts option to JSON and then\n\t// unmarshaling it to a map[string]string. This is done because the\n\t// k6 v0.42 changed Hosts from a map to types.NullHosts and doesn't\n\t// expose the map anymore.\n\t//\n\t// TODO: A better way to do this would be to handle the resolver\n\t// rules by communicating with Chromium (and then using Hosts's\n\t// Match method) instead of passing the rules via the command line\n\t// to Chromium.\n\tvar rules map[string]string\n\tb, err := json.Marshal(k6opts.Hosts)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshaling hosts option: %w\", err)\n\t}\n\tif err := json.Unmarshal(b, &rules); err != nil {\n\t\treturn fmt.Errorf(\"unmarshaling hosts option: %w\", err)\n\t}\n\tfor k, v := range rules {\n\t\thostResolver = append(hostResolver, fmt.Sprintf(\"MAP %s %s\", k, v))\n\t}\n\tif len(hostResolver) > 0 {\n\t\tsort.Strings(hostResolver)\n\t\tflags[\"host-resolver-rules\"] = strings.Join(hostResolver, \",\")\n\t}\n\n\treturn nil\n}\n\n// makeLogger makes and returns an extension wide logger.\nfunc makeLogger(ctx context.Context, envLookup env.LookupFunc) (*log.Logger, error) {\n\tvar (\n\t\tk6Logger = k6ext.GetVU(ctx).State().Logger\n\t\tlogger   = log.New(k6Logger, common.GetIterationID(ctx))\n\t)","sourceCodeStart":519,"sourceCodeEnd":555,"githubUrl":"https://github.com/grafana/k6/blob/8d06114777191d21ef885d05c5f8aa9e1ed838ae/internal/js/modules/k6/browser/chromium/browser_type.go#L519-L555","documentation":"After marshaling the k6 hosts option to JSON, the browser module unmarshals it into map[string]string to build Chromium's --host-resolver-rules. This error means the unmarshal step produced something that is not a string-to-string map. With the current types.NullHosts representation this is a defensive, near-unreachable path; it would only fire if the JSON shape of the hosts option ever stops being a flat string map.","triggerScenarios":"Same launch path as [600]: browser start with the `hosts` option set, and the marshaled JSON of NullHosts not decoding into map[string]string. Realistic only across k6 versions whose hosts option JSON representation changed, or with values that are not strings.","commonSituations":"Version-skewed k6 installs; custom builds; effectively never on released k6 because NullHosts always encodes as {\"host\":\"ip\"}.","solutions":["Check the hosts option for non-string values (numbers, objects) and make every value a plain string","Remove the hosts option and re-run to isolate the trigger","Align k6 and the browser module on one version (upgrade)","Report upstream with the exact hosts value if it reproduces on stock k6"],"exampleFix":"// before\nexport const options = { hosts: { 'test.k6.io': 12345 } };\n\n// after\nexport const options = { hosts: { 'test.k6.io': '1.2.3.4:80' } };","handlingStrategy":"validation","validationCode":"// Validate that every hosts value is a string before launching\nconst hosts = (options.hosts ||= {});\nfor (const [k, v] of Object.entries(hosts)) {\n  if (typeof v !== 'string') throw new Error(`hosts['${k}'] must be a string, got ${typeof v}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never put numbers or objects into the hosts option","Re-verify hosts config after k6 upgrades","Report reproduction cases on stock k6 upstream"],"tags":["hosts","options","json","browser","deserialization"],"backgroundTag":"json-deserialization-failed","analyzedSha":"8d06114777191d21ef885d05c5f8aa9e1ed838ae","analyzedAt":"2026-08-18T03:05:52.393Z","contentChangedAt":"2026-08-18T03:05:52.393Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}