{"record":{"id":"41007d34f2d973e1","repo":"juanfont/headscale","slug":"group-must-start-with-group","errorCode":null,"errorMessage":"group must start with 'group:'","messagePattern":"group must start with 'group:'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":111,"sourceCode":"\tErrNodeAttrTargetUnsupported    = errors.New(\"nodeAttrs target alias type is not supported\")\n)\n\n// nodeAttrUnsupportedCaps lists caps that headscale parses but cannot act on\n// today. Each entry maps to the tracking issue an operator can follow. The\n// caps are accepted by Tailscale SaaS, but delivering them via headscale\n// without the matching server-side machinery would be misleading — nodes\n// would advertise a feature that does not work. Reject at policy load and\n// point operators at the issue.\nvar nodeAttrUnsupportedCaps = map[tailcfg.NodeCapability]string{\n\ttailcfg.NodeAttrFunnel: \"https://github.com/juanfont/headscale/issues/2527\",\n}\n\n// Policy validation errors.\nvar (\n\tErrInvalidUsername             = errors.New(\"username must contain @\")\n\tErrUserNotFound                = errors.New(\"user not found\")\n\tErrMultipleUsersFound          = errors.New(\"multiple users found\")\n\tErrInvalidGroupFormat          = errors.New(\"group must start with 'group:'\")\n\tErrInvalidTagFormat            = errors.New(\"tag must start with 'tag:'\")\n\tErrInvalidHostname             = errors.New(\"invalid hostname\")\n\tErrHostResolve                 = errors.New(\"error resolving host\")\n\tErrInvalidPrefix               = errors.New(\"invalid prefix\")\n\tErrInvalidAutogroup            = errors.New(\"invalid autogroup\")\n\tErrUnknownAutogroup            = errors.New(\"unknown autogroup\")\n\tErrHostportMissingColon        = errors.New(\"hostport must contain a colon\")\n\tErrTypeNotSupported            = errors.New(\"type not supported\")\n\tErrInvalidAlias                = errors.New(\"invalid alias format\")\n\tErrInvalidAutoApprover         = errors.New(\"invalid auto approver format\")\n\tErrInvalidOwner                = errors.New(\"invalid owner format\")\n\tErrGroupNotDefined             = errors.New(\"group not defined in policy\")\n\tErrInvalidGroupMember          = errors.New(\"invalid group member type\")\n\tErrGroupValueNotArray          = errors.New(\"group value must be an array of users\")\n\tErrInvalidHostIP               = errors.New(\"hostname contains invalid IP address\")\n\tErrTagNotDefined               = errors.New(\"tag not found\")\n\tErrAutoApproverNotAlias        = errors.New(\"auto approver is not an alias\")\n\tErrInvalidACLAction            = errors.New(\"invalid ACL action\")","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L93-L129","documentation":"Group.Validate (hscontrol/policy/v2/types.go:461) requires every group key to start with the literal prefix 'group:'. The prefix is how the policy grammar distinguishes groups from usernames, tags, and hosts in alias positions, so a key without it fails at UnmarshalJSON time.","triggerScenarios":"Defining {\"groups\": {\"admins\": [...]}} instead of {\"groups\": {\"group:admins\": [...]}}; also when a group key contains the prefix in the wrong case or position ('Group:admins'). Fails during policy parse, before any resolution.","commonSituations":"Writing a first ACL file following generic Tailscale examples that omit the prefix; renaming groups and dropping the prefix; JSON key typos after hand-editing.","solutions":["Rename the group key to start with 'group:'","Keep the prefix when referencing the group elsewhere (src/dst/owners)","Run 'headscale policy check' on the file before applying to catch parse errors early"],"exampleFix":"// before\n{\"groups\": {\"admins\": [\"alice@example.com\"]}}\n// after\n{\"groups\": {\"group:admins\": [\"alice@example.com\"]}}","handlingStrategy":"validation","validationCode":"for key := range policy.Groups {\n    if !strings.HasPrefix(key, \"group:\") {\n        return fmt.Errorf(\"group key %q must start with 'group:'\", key)\n    }\n}","typeGuard":"func isGroupKey(s string) bool { return strings.HasPrefix(s, \"group:\") }","tryCatchPattern":"if errors.Is(err, policy.ErrInvalidGroupFormat) {\n    // rename the key adding the group: prefix\n}","preventionTips":["Always define groups with the group: prefix","Start from a known-good policy template","Lint group keys in CI"],"tags":["headscale","policy","groups","acl","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}