juanfont/headscale · error

invalid group member type

Error message

invalid group member type

What it means

Group member parsing (hscontrol/policy/v2/types.go:1346) requires every item in a group's member array to be a JSON string. A non-string element (number, object, nested array) yields this sentinel with the group key and the element's Go %T. Note the sibling ErrGroupValueNotArray covers the whole value not being an array at all.

Source

Thrown at hscontrol/policy/v2/types.go:124

// Policy validation errors.
var (
	ErrInvalidUsername             = errors.New("username must contain @")
	ErrUserNotFound                = errors.New("user not found")
	ErrMultipleUsersFound          = errors.New("multiple users found")
	ErrInvalidGroupFormat          = errors.New("group must start with 'group:'")
	ErrInvalidTagFormat            = errors.New("tag must start with 'tag:'")
	ErrInvalidHostname             = errors.New("invalid hostname")
	ErrHostResolve                 = errors.New("error resolving host")
	ErrInvalidPrefix               = errors.New("invalid prefix")
	ErrInvalidAutogroup            = errors.New("invalid autogroup")
	ErrUnknownAutogroup            = errors.New("unknown autogroup")
	ErrHostportMissingColon        = errors.New("hostport must contain a colon")
	ErrTypeNotSupported            = errors.New("type not supported")
	ErrInvalidAlias                = errors.New("invalid alias format")
	ErrInvalidAutoApprover         = errors.New("invalid auto approver format")
	ErrInvalidOwner                = errors.New("invalid owner format")
	ErrGroupNotDefined             = errors.New("group not defined in policy")
	ErrInvalidGroupMember          = errors.New("invalid group member type")
	ErrGroupValueNotArray          = errors.New("group value must be an array of users")
	ErrInvalidHostIP               = errors.New("hostname contains invalid IP address")
	ErrTagNotDefined               = errors.New("tag not found")
	ErrAutoApproverNotAlias        = errors.New("auto approver is not an alias")
	ErrInvalidACLAction            = errors.New("invalid ACL action")
	ErrInvalidSSHAction            = errors.New("invalid SSH action")
	ErrInvalidProtocolNumber       = errors.New("invalid protocol number")
	ErrProtocolLeadingZero         = errors.New("leading 0 not permitted in protocol number")
	ErrProtocolOutOfRange          = errors.New("protocol number out of range (0-255)")
	ErrAutogroupNotSupported       = errors.New("autogroup not supported in headscale")
	ErrAutogroupInternetSrc        = errors.New("autogroup:internet can only be used in ACL destinations")
	ErrAutogroupSelfSrc            = errors.New("\"autogroup:self\" not valid on the src side of a rule")
	ErrAutogroupNotSupportedACLSrc = errors.New("autogroup not supported for ACL sources")
	ErrAutogroupNotSupportedACLDst = errors.New("autogroup not supported for ACL destinations")
	ErrAutogroupDangerAllDst       = errors.New("cannot use autogroup:danger-all as a dst")
	ErrAutogroupNotSupportedSSHSrc = errors.New("autogroup not supported for SSH sources")
	ErrAutogroupNotSupportedSSHDst = errors.New("autogroup not supported for SSH destinations")
	ErrHostNotDefined              = errors.New("host not defined in policy")

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Make every group member a plain string username/email
  2. Fix the generator to serialize all members as strings
  3. Lint generated policy JSON before applying

Example fix

// before
{"groups": {"group:admins": ["alice@", 7]}}
// after
{"groups": {"group:admins": ["alice@", "bob@"]}}
Defensive patterns

Strategy: type-guard

Validate before calling

for _, g := range rawGroups {
    for _, m := range g {
        if _, ok := m.(string); !ok {
            return fmt.Errorf("group member must be string, got %T", m)
        }
    }
}

Type guard

func isStringMember(v any) bool { _, ok := v.(string); return ok }

Try / catch

if errors.Is(err, policy.ErrInvalidGroupMember) {
    // %T names the bad element; make every member a quoted string
}

Prevention

When it happens

Trigger: {"groups": {"group:admins": ["alice@", 42]}} or a member rendered as {"user": "alice"} by a template/code generator. Fails while unmarshalling the groups map.

Common situations: Programmatic policy generation emitting mixed-type arrays; YAML-to-JSON conversion turning a scalar into a non-string; hand-editing adding a numeric user ID or comment object inside the member list.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/85cb1ab70e5e7eb1. Report an issue: GitHub.