juanfont/headscale · error
references undefined tag
Error message
references undefined tag
What it means
Returned while resolving tag owners (hscontrol/policy/v2/policy.go:1674) as 'tag %q references undefined tag %q': a tagOwners entry names a tag that is not defined in the policy's tags section. Tag owners may be users, groups, or other tags, but every referenced tag must itself exist.
Source
Thrown at hscontrol/policy/v2/types.go:38
"tailscale.com/tailcfg"
"tailscale.com/types/views"
"tailscale.com/util/multierr"
"tailscale.com/util/set"
"tailscale.com/util/slicesx"
)
// Global JSON options for consistent parsing across all struct unmarshaling.
var policyJSONOpts = []json.Options{
json.DefaultOptionsV2(),
json.MatchCaseInsensitiveNames(true),
json.RejectUnknownMembers(true),
}
const Wildcard = Asterix(0)
var ErrAutogroupSelfRequiresPerNodeResolution = errors.New("autogroup:self requires per-node resolution and cannot be resolved in this context")
var ErrUndefinedTagReference = errors.New("references undefined tag")
// SSH validation errors.
var (
ErrSSHTagSourceToUserDest = errors.New("tags in SSH source cannot access user-owned devices")
ErrSSHUserDestRequiresSameUser = errors.New("user destination requires source to contain only that same user")
ErrSSHAutogroupSelfRequiresUserSource = errors.New("autogroup:self destination requires source to contain only users or groups, not tags or autogroup:tagged")
ErrSSHTagSourceToAutogroupMember = errors.New("tags in SSH source cannot access autogroup:member (user-owned devices)")
ErrSSHWildcardDestination = errors.New("wildcard (*) is not supported as SSH destination")
ErrSSHCheckPeriodAboveMax = errors.New("is above the max (168h)")
ErrSSHCheckPeriodNegative = errors.New("must be a positive duration")
ErrSSHCheckPeriodOnNonCheck = errors.New("checkPeriod is only valid with action \"check\"")
ErrInvalidLocalpart = errors.New("invalid localpart format, must be localpart:*@<domain>")
ErrSSHUsersMustBeSpecified = errors.New("users must be specified")
ErrSSHUserInvalid = errors.New("is not valid")
ErrSSHAcceptEnvEmpty = errors.New("acceptEnv values cannot be empty")
ErrSSHActionMustBeSpecified = errors.New("action must be specified")
ErrSSHActionInvalid = errors.New("is not a valid action")
ErrSSHDestinationHostAlias = errors.New("invalid dst")View on GitHub (pinned to 565fd254d0)
Solutions
- Add the missing tag to the tags block (or fix the typo in the reference)
- Remove the reference if the owning tag no longer exists
- Run 'headscale policy check' — the error names both the referencing and the undefined tag
Example fix
// before
"tagOwners": {"tag:proxy": ["tag:gateway"]}
// after
"tagOwners": {"tag:proxy": ["tag:gateway"], "tag:gateway": ["group:admins"]} Defensive patterns
Strategy: validation
Validate before calling
func allReferencedTagsDefined(tagOwners map[string][]string, tags map[string][]string) bool { defined := map[string]bool{}; for t := range tags { defined[t] = true }; for t := range tagOwners { defined[t] = true }; for _, owners := range tagOwners { for _, o := range owners { if strings.HasPrefix(o, "tag:") && !defined[o] { return false } } }; return true } Prevention
- Define every tag in the tags block before referencing it in tagOwners
- Search-and-destroy stale tag references after renames
- Run 'headscale policy check' before applying
When it happens
Trigger: "tagOwners": {"tag:proxy": ["tag:gateway"]} where "tag:gateway" has no entry in the tags block (nor as a key elsewhere in tagOwners defining it).
Common situations: Renaming or deleting a tag but leaving stale references in tagOwners; merging policy fragments where the tags block was dropped; typo'd tag names.
Related errors
- invalid action
- autogroup:self requires per-node resolution and cannot be re
- wildcard (*) is not supported as SSH destination
- is above the max (168h)
- tag must start with 'tag:'
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/3ab200de57074d6f.
Report an issue: GitHub.