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

  1. Add the missing tag to the tags block (or fix the typo in the reference)
  2. Remove the reference if the owning tag no longer exists
  3. 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

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


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