{"record":{"id":"c8736cc4b1f149d5","repo":"slackhq/nebula","slug":"tun-routes-is-not-an-array","errorCode":null,"errorMessage":"tun.routes is not an array","messagePattern":"tun\\.routes is not an array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"overlay/route.go","lineNumber":80,"sourceCode":"\t\tif len(gateways) > 0 {\n\t\t\trouting.CalculateBucketsForGateways(gateways)\n\t\t\trouteTree.Insert(r.Cidr, gateways)\n\t\t}\n\t}\n\treturn routeTree, nil\n}\n\nfunc parseRoutes(c *config.C, networks []netip.Prefix) ([]Route, error) {\n\tvar err error\n\n\tr := c.Get(\"tun.routes\")\n\tif r == nil {\n\t\treturn []Route{}, nil\n\t}\n\n\trawRoutes, ok := r.([]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"tun.routes is not an array\")\n\t}\n\n\tif len(rawRoutes) < 1 {\n\t\treturn []Route{}, nil\n\t}\n\n\troutes := make([]Route, len(rawRoutes))\n\tfor i, r := range rawRoutes {\n\t\tm, ok := r.(map[string]any)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"entry %v in tun.routes is invalid\", i+1)\n\t\t}\n\n\t\trMtu, ok := m[\"mtu\"]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.mtu in tun.routes is not present\", i+1)\n\t\t}\n","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/overlay/route.go#L62-L98","documentation":"The config value at tun.routes is not a JSON/YAML array. parseRoutes receives the decoded config field and first asserts it is []any; if the cast fails the library rejects the configuration because routes can only be expressed as a list of objects.","triggerScenarios":"Calling getAllRoutesFromConfig (during config load) when the tun.routes field is set to a scalar, string, object/map, or null-typed non-array value (e.g. tun.routes: \"10.0.0.0/24\" or a single route object instead of a list).","commonSituations":"Hand-edited config where the user wrote a single route without wrapping it in a list; YAML/JSON indentation mistakes that collapse routes into one map; tooling or templating that emits a string instead of an array.","solutions":["Change tun.routes in the config to be a list (array) of route objects","Check YAML indentation so each route entry is a separate list item","Validate the config file with a JSON/YAML schema or linter before startup","If only one route is needed, still wrap it: tun.routes: [{mtu: 1300, route: 10.0.0.0/24}]"],"exampleFix":"// before (YAML)\ntun:\n  routes:\n    mtu: 1300\n    route: 10.0.0.0/24\n// after\ntun:\n  routes:\n    - mtu: 1300\n      route: 10.0.0.0/24","handlingStrategy":"validation","validationCode":"raw, ok := conf[\"tun\"].(map[string]any)[\"routes\"]\nif raw != nil {\n    if _, ok := raw.([]any); !ok {\n        return fmt.Errorf(\"tun.routes must be a list of route objects\")\n    }\n}\n// run before getAllRoutesFromConfig/loadConfig applies","typeGuard":"func isRouteArray(v any) bool {\n    _, ok := v.([]any)\n    return ok\n}","tryCatchPattern":null,"preventionTips":["Always express tun.routes as a YAML/JSON list","Lint configs with a schema enforcing array type","Watch YAML indentation so entries stay list items","Test config load in CI before deployment"],"tags":["config","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}