{"record":{"id":"3ab200de57074d6f","repo":"juanfont/headscale","slug":"references-undefined-tag","errorCode":null,"errorMessage":"references undefined tag","messagePattern":"references undefined tag","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":38,"sourceCode":"\t\"tailscale.com/tailcfg\"\n\t\"tailscale.com/types/views\"\n\t\"tailscale.com/util/multierr\"\n\t\"tailscale.com/util/set\"\n\t\"tailscale.com/util/slicesx\"\n)\n\n// Global JSON options for consistent parsing across all struct unmarshaling.\nvar policyJSONOpts = []json.Options{\n\tjson.DefaultOptionsV2(),\n\tjson.MatchCaseInsensitiveNames(true),\n\tjson.RejectUnknownMembers(true),\n}\n\nconst Wildcard = Asterix(0)\n\nvar ErrAutogroupSelfRequiresPerNodeResolution = errors.New(\"autogroup:self requires per-node resolution and cannot be resolved in this context\")\n\nvar ErrUndefinedTagReference = errors.New(\"references undefined tag\")\n\n// SSH validation errors.\nvar (\n\tErrSSHTagSourceToUserDest             = errors.New(\"tags in SSH source cannot access user-owned devices\")\n\tErrSSHUserDestRequiresSameUser        = errors.New(\"user destination requires source to contain only that same user\")\n\tErrSSHAutogroupSelfRequiresUserSource = errors.New(\"autogroup:self destination requires source to contain only users or groups, not tags or autogroup:tagged\")\n\tErrSSHTagSourceToAutogroupMember      = errors.New(\"tags in SSH source cannot access autogroup:member (user-owned devices)\")\n\tErrSSHWildcardDestination             = errors.New(\"wildcard (*) is not supported as SSH destination\")\n\tErrSSHCheckPeriodAboveMax             = errors.New(\"is above the max (168h)\")\n\tErrSSHCheckPeriodNegative             = errors.New(\"must be a positive duration\")\n\tErrSSHCheckPeriodOnNonCheck           = errors.New(\"checkPeriod is only valid with action \\\"check\\\"\")\n\tErrInvalidLocalpart                   = errors.New(\"invalid localpart format, must be localpart:*@<domain>\")\n\tErrSSHUsersMustBeSpecified            = errors.New(\"users must be specified\")\n\tErrSSHUserInvalid                     = errors.New(\"is not valid\")\n\tErrSSHAcceptEnvEmpty                  = errors.New(\"acceptEnv values cannot be empty\")\n\tErrSSHActionMustBeSpecified           = errors.New(\"action must be specified\")\n\tErrSSHActionInvalid                   = errors.New(\"is not a valid action\")\n\tErrSSHDestinationHostAlias            = errors.New(\"invalid dst\")","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L20-L56","documentation":"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.","triggerScenarios":"\"tagOwners\": {\"tag:proxy\": [\"tag:gateway\"]} where \"tag:gateway\" has no entry in the tags block (nor as a key elsewhere in tagOwners defining it).","commonSituations":"Renaming or deleting a tag but leaving stale references in tagOwners; merging policy fragments where the tags block was dropped; typo'd tag names.","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"],"exampleFix":"// before\n\"tagOwners\": {\"tag:proxy\": [\"tag:gateway\"]}\n\n// after\n\"tagOwners\": {\"tag:proxy\": [\"tag:gateway\"], \"tag:gateway\": [\"group:admins\"]}","handlingStrategy":"validation","validationCode":"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 }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["policy","tags","config","headscale"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}