juanfont/headscale · error

invalid owner format

Error message

invalid owner format

What it means

Owner parsing in the tagOwners path (hscontrol/policy/v2/types.go:1290) validates each owner token; one that is not a valid owner alias (user with '@', group:, or wildcard forms) yields this sentinel with the offending string. Owners grant the right to assign tags, so they must be identities.

Source

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

}

// 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")

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Make every owner a user ('name@' or email) or 'group:name'
  2. Remove hosts, IPs, and tags from owner lists
  3. Prefer groups for owner lists to ease maintenance

Example fix

// before
{"tagOwners": {"tag:web": ["webserver"]}}
// after
{"tagOwners": {"tag:web": ["group:admins"]}}
Defensive patterns

Strategy: validation

Validate before calling

func isValidOwner(s string) bool {
    return strings.HasPrefix(s, "group:") || strings.Contains(s, "@")
}
for _, owners := range policy.TagOwners {
    for _, o := range owners { if !isValidOwner(o) { return fmt.Errorf("bad owner %q", o) } }
}

Type guard

func isOwnerToken(s string) bool { return isValidOwner(s) }

Try / catch

if errors.Is(err, policy.ErrInvalidOwner) {
    // owner must be user (@) or group; remove hosts/tags/IPs
}

Prevention

When it happens

Trigger: {"tagOwners": {"tag:web": ["webserver"]}} — a hostname or bare name without '@' as an owner. Also IP addresses or autogroup tokens in owner position, which the owner parser does not accept.

Common situations: Putting machines/tags in tagOwners instead of users/groups; forgetting '@' on bare usernames (overlaps ErrInvalidUsername semantics but surfaces here when reached via owner parsing); copying dst aliases into owner slots.

Related errors


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