{"record":{"id":"4f24f7dde3473eb1","repo":"AdguardTeam/AdGuardHome","slug":"unexpected-type-of-upstream-field-t","errorCode":null,"errorMessage":"unexpected type of upstream field: %T","messagePattern":"unexpected type of upstream field: %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/configmigrate/v10.go","lineNumber":75,"sourceCode":"\t\treturn err\n\t} else if ok {\n\t\tif err = addQUICPorts(ups, quicPort); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdns[\"local_ptr_upstreams\"] = ups\n\t}\n\n\treturn nil\n}\n\n// addQUICPorts inserts a port into each QUIC upstream's hostname in ups if\n// those are missing.\nfunc addQUICPorts(ups yarr, port int) (err error) {\n\tfor i, uVal := range ups {\n\t\tu, ok := uVal.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type of upstream field: %T\", uVal)\n\t\t}\n\n\t\tups[i] = addQUICPort(u, port)\n\t}\n\n\treturn nil\n}\n\n// addQUICPort inserts a port into QUIC upstream's hostname if it is missing.\nfunc addQUICPort(ups string, port int) (withPort string) {\n\tif ups == \"\" || ups[0] == '#' {\n\t\treturn ups\n\t}\n\n\tvar doms string\n\twithPort = ups\n\tif after, ok := strings.CutPrefix(ups, \"[/\"); ok {\n\t\tdomsAndUps := strings.Split(after, \"/]\")","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/AdguardTeam/AdGuardHome/blob/b41aefbe51c8dde65e2c50f093996afa0502edf9/internal/configmigrate/v10.go#L57-L93","documentation":"During migration to schema 10, addQUICPorts found an entry in the upstreams array that is not a string. Each upstream must be a YAML string so a QUIC port can be appended; any other YAML type aborts the migration.","triggerScenarios":"Migrating a config where the dns.upstreams (or equivalent) array contains a non-string element, e.g. `- 53` (int) or a nested mapping `- url: ...`, while QUIC port insertion is applied.","commonSituations":"Machine-generated or hand-converted configs that emit numbers or objects for upstream entries; configs converted from JSON with lossy typing.","solutions":["Open the config and make every element of upstreams a quoted string (\"dns://…\")","Re-export the config from AdGuard Home itself so upstream entries are strings","Retry the migration after fixing the offending entry"],"exampleFix":"# before\nupstreams:\n  - quic://dns.adguard.com\n  - 53\n\n# after\nupstreams:\n  - quic://dns.adguard.com:853\n  - \"53\"","handlingStrategy":"type-guard","validationCode":"for _, u := range upstreams {\n    if _, ok := u.(string); !ok { /* fix: quote all upstream entries */ }\n}","typeGuard":"func isStringArray(a []any) bool {\n    for _, v := range a {\n        if _, ok := v.(string); !ok { return false }\n    }\n    return true\n}","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"unexpected type of upstream field\") { /* quote numeric entries and re-migrate */ }","preventionTips":["Always quote upstream values in YAML","Generate configs with the tool itself, not scripts that emit unquoted scalars"],"tags":["config","migration","upstream","yaml","type-mismatch"],"backgroundTag":"config-field-type-mismatch","analyzedSha":"b41aefbe51c8dde65e2c50f093996afa0502edf9","analyzedAt":"2026-08-27T04:57:55.097Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}