{"record":{"id":"8df0130f6f3661f1","repo":"juanfont/headscale","slug":"groups-q-q-w","errorCode":null,"errorMessage":"groups[%q]: %q: %w","messagePattern":"groups\\[%q\\]: %q: %w","errorType":"validation","errorClass":"ErrGroupMembersCannotBeRecursive","httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":1372,"sourceCode":"\t\t\treturn fmt.Errorf(\"%w: group %q got %T\", ErrGroupValueNotArray, key, v)\n\t\t}\n\t}\n\n\t// Reject group-in-group references. Reverse-sort the keys so the\n\t// reported (parent, child) pair names the deepest non-leaf parent\n\t// first.\n\tkeys := make([]string, 0, len(rawGroups))\n\tfor k := range rawGroups {\n\t\tkeys = append(keys, k)\n\t}\n\n\tslices.Sort(keys)\n\tslices.Reverse(keys)\n\n\tfor _, key := range keys {\n\t\tfor _, u := range rawGroups[key] {\n\t\t\tif isGroup(u) {\n\t\t\t\treturn fmt.Errorf(\"groups[%q]: %q: %w\", key, u, ErrGroupMembersCannotBeRecursive)\n\t\t\t}\n\t\t}\n\t}\n\n\t*g = make(Groups)\n\n\tfor key, value := range rawGroups {\n\t\tgroup := Group(key)\n\t\t// Group name already validated above\n\t\tvar usernames Usernames\n\n\t\tfor _, u := range value {\n\t\t\tusername := Username(u)\n\n\t\t\terr := username.Validate()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}","sourceCodeStart":1354,"sourceCodeEnd":1390,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L1354-L1390","documentation":"Groups may not reference other groups (ErrGroupMembersCannotBeRecursive). After unmarshalling, each member is checked with isGroup and any nested group reference is rejected, naming both the containing group and the member.","triggerScenarios":"{\"group:a\": [\"group:b\"]} anywhere in the groups map — headscale, like Tailscale SaaS, flattens groups only over users.","commonSituations":"Trying to build group hierarchies (e.g. group:all containing group:eng and group:sales); migrating from systems that permit nested groups.","solutions":["Flatten the nested group: list the individual user emails directly in the outer group","Reference both groups in the ACL rule's src list instead of nesting them"],"exampleFix":"// before\n{\"group:all\": [\"group:eng\", \"group:sales\"]}\n// after\n{\"group:all\": [\"a@ex.com\", \"b@ex.com\"]}\n// or in the rule:\n\"src\": [\"group:eng\", \"group:sales\"]","handlingStrategy":"validation","validationCode":"func noNestedGroups(groups map[string][]string) bool {\n\tfor _, members := range groups {\n\t\tfor _, m := range members { if strings.HasPrefix(m, \"group:\") { return false } }\n\t}\n\treturn true\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Flatten group hierarchies at authoring time — groups contain users only","Compose groups at the rule level by listing several groups in src"],"tags":["policy","groups","recursion"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}