{"record":{"id":"2b8fbc3ec29eb8ac","repo":"juanfont/headscale","slug":"group-value-must-be-an-array-of-users","errorCode":null,"errorMessage":"group value must be an array of users","messagePattern":"group value must be an array of users","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":125,"sourceCode":"var (\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\")\n\tErrInvalidSSHAction            = errors.New(\"invalid SSH action\")\n\tErrInvalidProtocolNumber       = errors.New(\"invalid protocol number\")\n\tErrProtocolLeadingZero         = errors.New(\"leading 0 not permitted in protocol number\")\n\tErrProtocolOutOfRange          = errors.New(\"protocol number out of range (0-255)\")\n\tErrAutogroupNotSupported       = errors.New(\"autogroup not supported in headscale\")\n\tErrAutogroupInternetSrc        = errors.New(\"autogroup:internet can only be used in ACL destinations\")\n\tErrAutogroupSelfSrc            = errors.New(\"\\\"autogroup:self\\\" not valid on the src side of a rule\")\n\tErrAutogroupNotSupportedACLSrc = errors.New(\"autogroup not supported for ACL sources\")\n\tErrAutogroupNotSupportedACLDst = errors.New(\"autogroup not supported for ACL destinations\")\n\tErrAutogroupDangerAllDst       = errors.New(\"cannot use autogroup:danger-all as a dst\")\n\tErrAutogroupNotSupportedSSHSrc = errors.New(\"autogroup not supported for SSH sources\")\n\tErrAutogroupNotSupportedSSHDst = errors.New(\"autogroup not supported for SSH destinations\")\n\tErrHostNotDefined              = errors.New(\"host not defined in policy\")\n\tErrSSHSourceAliasNotSupported  = errors.New(\"alias not supported for SSH source\")","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L107-L143","documentation":"Returned by Groups.UnmarshalJSON (hscontrol/policy/v2/types.go:1352-1354) when a value in the policy's \"groups\" object is not a JSON array of strings. Headscale requires every group to map to a list of usernames (e.g. \"group:example\": [\"user@example.com\"]); a bare string, number, or object is rejected at policy-parse time. The error is wrapped with the offending group key so the failing entry is identifiable.","triggerScenarios":"A HuJSON/JSON policy containing \"groups\": {\"group:admin\": \"user@example.com\"} (string instead of array), or a group value that is a number/object. Surfaces when headscale loads policy from policy.path in the config, via `headscale policy set -f`, or when unmarshalling a Policy in Go code.","commonSituations":"Writing a group as a comma-separated string (YAML habits), copy-pasting from Tailscale ACL docs with mangled formatting, or hand-editing HuJSON and dropping the brackets. Fails at headscale startup or policy update, so the whole policy is rejected.","solutions":["Wrap the group members in square brackets: \"group:admin\": [\"user@example.com\"]","Ensure every element inside the array is a plain string; nested arrays or numbers produce ErrInvalidGroupMember instead","Validate the policy with `headscale policy check` (or re-run `headscale policy set`) after editing to confirm it parses"],"exampleFix":"// before (policy.hujson)\n\"groups\": {\n  \"group:admin\": \"user@example.com\"\n}\n\n// after\n\"groups\": {\n  \"group:admin\": [\"user@example.com\"]\n}","handlingStrategy":"validation","validationCode":"// Validate groups shape before loading the policy (Go caller)\nfunc checkGroupsShape(raw map[string]json.RawMessage) error {\n\tfor name, v := range raw[\"groups\"].(map[string]json.RawMessage) {\n\t\tvar arr []any\n\t\tif json.Unmarshal(v, &arr) != nil {\n\t\t\treturn fmt.Errorf(\"group %s value must be an array\", name)\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":"func isGroupValueNotArray(err error) bool {\n\treturn errors.Is(err, policy.ErrGroupValueNotArray)\n}","tryCatchPattern":"err := json.Unmarshal(policyBytes, &p)\nif err != nil {\n\tif errors.Is(err, policy.ErrGroupValueNotArray) {\n\t\t// point the user at the named group in the wrapped message\n\t\treturn fmt.Errorf(\"fix groups section: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Lint policies in CI with `headscale policy check` (or a JSON-schema check) before deploy","Always author groups as arrays of strings, even for a single member","Use HuJSON tooling/format-on-save so structural mistakes are visible while editing"],"tags":["policy","json","validation","headscale","groups"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}