{"record":{"id":"dd8da8a74900a4f7","repo":"juanfont/headscale","slug":"unmarshalling-dns-extra-records-w","errorCode":null,"errorMessage":"unmarshalling dns extra records: %w","messagePattern":"unmarshalling dns extra records: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/types/config.go","lineNumber":925,"sourceCode":"\t// err := viper.UnmarshalKey(\"dns\", &dns)\n\t// if err != nil {\n\t// \treturn DNSConfig{}, fmt.Errorf(\"unmarshalling dns config: %w\", err)\n\t// }\n\n\tdns.MagicDNS = viper.GetBool(\"dns.magic_dns\")\n\tdns.BaseDomain = viper.GetString(\"dns.base_domain\")\n\tdns.OverrideLocalDNS = viper.GetBool(\"dns.override_local_dns\")\n\tdns.Nameservers.Global = viper.GetStringSlice(\"dns.nameservers.global\")\n\tdns.Nameservers.Split = viper.GetStringMapStringSlice(\"dns.nameservers.split\")\n\tdns.SearchDomains = viper.GetStringSlice(\"dns.search_domains\")\n\tdns.ExtraRecordsPath = viper.GetString(\"dns.extra_records_path\")\n\n\tif viper.IsSet(\"dns.extra_records\") {\n\t\tvar extraRecords []tailcfg.DNSRecord\n\n\t\terr := viper.UnmarshalKey(\"dns.extra_records\", &extraRecords)\n\t\tif err != nil {\n\t\t\treturn DNSConfig{}, fmt.Errorf(\"unmarshalling dns extra records: %w\", err)\n\t\t}\n\n\t\tdns.ExtraRecords = extraRecords\n\t}\n\n\treturn dns, nil\n}\n\n// parseResolvers converts nameserver strings into DNS resolvers.\n// If a nameserver is a valid IP, it will be used as a regular resolver.\n// If a nameserver is a valid URL, it will be used as a DoH resolver.\n// If a nameserver is neither a valid URL nor a valid IP, it will be ignored.\n// When domain is non-empty, it is included in the warning for invalid entries.\nfunc parseResolvers(nameservers []string, domain string) []*dnstype.Resolver {\n\tvar resolvers []*dnstype.Resolver\n\n\tfor _, nsStr := range nameservers {\n\t\tif _, err := netip.ParseAddr(nsStr); err == nil { //nolint:noinlineerr","sourceCodeStart":907,"sourceCodeEnd":943,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/types/config.go#L907-L943","documentation":"viper.UnmarshalKey(\"dns.extra_records\", &[]tailcfg.DNSRecord) failed while loading DNS config: the configured value's shape does not match a list of DNS records (name + type + value). Emitted from loadDNSConfig, so the whole DNS config load fails at startup or reload.","triggerScenarios":"dns.extra_records set to a mapping instead of a list ({...} vs [...]), records missing required fields, or values of the wrong type (e.g. value: 3600 where a string is expected). Only fires when viper.IsSet sees the key.","commonSituations":"Converting older headscale configs where extra_records used a different schema; YAML indentation making a record a nested map; copy-pasting a record from docs with the wrong field names (data vs value).","solutions":["Use the documented list-of-mappings form with name/type/value keys (see the extra_records example in config-example.yaml)","Validate the YAML structure with yamllint before restart","Ensure type is a valid DNS record type string like A, AAAA, or TXT"],"exampleFix":"# before\ndns:\n  extra_records:\n    example.com: { type: A, value: 1.2.3.4 }\n\n# after\ndns:\n  extra_records:\n    - name: example.com\n      type: A\n      value: 1.2.3.4","handlingStrategy":"validation","validationCode":"// Structural pre-check mirroring the unmarshal target:\nrecs := viper.Get(\"dns.extra_records\")\nif recs != nil {\n    if _, ok := recs.([]any); !ok {\n        return errors.New(\"dns.extra_records must be a list of {name,type,value} mappings\")\n    }\n}","typeGuard":"func isValidExtraRecords(v any) bool {\n    list, ok := v.([]any)\n    if !ok { return false }\n    for _, r := range list {\n        m, ok := r.(map[string]any)\n        if !ok { return false }\n        if _, ok := m[\"name\"]; !ok { return false }\n        if _, ok := m[\"value\"]; !ok { return false }\n    }\n    return true\n}","tryCatchPattern":null,"preventionTips":["Write extra_records as a YAML list of mappings with name/type/value keys","Quote record values that look like numbers or contain colons","Validate rendered config in CI with a YAML schema check"],"tags":["config","dns","yaml","validation","go"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}