juanfont/headscale · error
tag not found
Error message
tag not found
What it means
Returned by TagOwners.Contains (hscontrol/policy/v2/types.go:1488) and surfaced from Policy.validate when a tag: alias is used in a rule but has no corresponding entry in the policy's "tagOwners" map. Headscale (like Tailscale) requires every tag referenced in ACLs or SSH rules to be owned by at least one user or group, otherwise nodes cannot claim it.
Source
Thrown at hscontrol/policy/v2/types.go:127
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")
ErrSSHSourceAliasNotSupported = errors.New("alias not supported for SSH source")
ErrSSHDestAliasNotSupported = errors.New("alias not supported for SSH destination")
ErrUnknownField = errors.New("unknown field")View on GitHub (pinned to 565fd254d0)
Solutions
- Add a tagOwners entry granting an existing user/group ownership: "tagOwners": {"tag:server": ["group:admin"]}
- Verify the tag name matches exactly (case-sensitive, must start with "tag:") in both tagOwners and the rule
- If the tag is obsolete, remove its references from acls/ssh sections instead
Example fix
// before
"acls": [{"action": "accept", "src": ["tag:server"], "dst": ["*:*"]}]
// after
"tagOwners": {"tag:server": ["group:admin"]},
"acls": [{"action": "accept", "src": ["tag:server"], "dst": ["*:*"]}] Defensive patterns
Strategy: validation
Validate before calling
// Verify every tag referenced in rules has an owner before loading
func tagsReferenced(p map[string]any) map[string]bool {
refs := map[string]bool{}
for _, section := range []string{"acls", "ssh", "tests"} {
// walk strings starting with "tag:" in src/dst arrays
}
return refs
}
// then: every ref must be a key of tagOwners Type guard
func isTagNotDefined(err error) bool {
return errors.Is(err, policy.ErrTagNotDefined)
} Try / catch
if err := p.Validate(); err != nil {
if errors.Is(err, policy.ErrTagNotDefined) {
// wrapped message contains the missing tag name
return fmt.Errorf("policy rejected, add tagOwners entry: %w", err)
}
return err
} Prevention
- Declare tagOwners for every tag at the moment you first use the tag
- Use a naming convention (tag:<service>:<role>) and grep policy files for tag: references before renaming
- Keep policy under version control and diff tagOwners alongside acls in review
When it happens
Trigger: Policy with "acls": [{"action": "accept", "src": ["tag:server"], "dst": [...]}] but no "tagOwners": {"tag:server": [...]}. Also fires for tags used in SSH rules and (per the TODO at types.go:1997) will increasingly cover dst sides. Reported as "src=tag not found: ..." when raised from an ACL source check.
Common situations: Adding a new tag to ACLs and forgetting to grant ownership; renaming a tag in tagOwners but not in the rules; migrating from a policy style that pre-declared tags elsewhere. Nodes using the tag fail to register until fixed.
Related errors
- tag must start with 'tag:'
- test(s) failed
- nodeAttrs target does not support this autogroup
- username must contain @
- group must start with 'group:'
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/48f7494a77d0cb4d.
Report an issue: GitHub.