{"record":{"id":"85c010dac20f42e5","repo":"juanfont/headscale","slug":"autogroup-self-requires-per-node-resolution-and-ca","errorCode":null,"errorMessage":"autogroup:self requires per-node resolution and cannot be resolved in this context","messagePattern":"autogroup:self requires per-node resolution and cannot be resolved in this context","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":36,"sourceCode":"\t\"go4.org/netipx\"\n\t\"tailscale.com/net/tsaddr\"\n\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\")","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L18-L54","documentation":"Returned from alias resolution (hscontrol/policy/v2/types.go:811) when autogroup:self is used where a global, node-independent answer is required. autogroup:self means 'the requesting node's own user', which can only be resolved per-node (e.g. in filter compilation for a specific node); contexts that compile one answer for the whole tailnet cannot honor it.","triggerScenarios":"Using autogroup:self in a position resolved globally: as a tag owner, inside SSH destination resolution, in tests evaluation paths that need a flat IP set, or any Resolve() call without per-node context.","commonSituations":"Copy-pasting an autogroup from a grants src (valid) into tagOwners or an SSH dst (invalid); upgrading policies from examples that used * and reaching for autogroup:self as the replacement everywhere.","solutions":["Remove autogroup:self from the offending position — check tagOwners and SSH destinations first","In grants srcs it is fine; replace dst usages with autogroup:member, autogroup:tagged, or explicit users/tags","Use 'headscale policy check' to get the exact location"],"exampleFix":"// before\n\"tagOwners\": {\"tag:app\": [\"autogroup:self\"]}\n\n// after\n\"tagOwners\": {\"tag:app\": [\"group:admins\"]}","handlingStrategy":"validation","validationCode":"func autogroupSelfAllowedIn(position string) bool { return position == \"grants.src\" || position == \"ssh.src\" }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reserve autogroup:self for per-node-resolved positions (grant/SSH sources)","Use autogroup:member/autogroup:tagged for dsts and owners","Validate policy files in CI to catch misplaced autogroups"],"tags":["policy","autogroup","config","headscale"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}