{"record":{"id":"f69d21fc6cd57582","repo":"juanfont/headscale","slug":"w-q-t","errorCode":null,"errorMessage":"%w: %q (%T)","messagePattern":"%w: %q \\(%T\\)","errorType":"validation","errorClass":"ErrNodeAttrTargetUnsupported","httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":2707,"sourceCode":"\t\t\t\t}\n\t\t\tcase *Group:\n\t\t\t\terr := p.Groups.Contains(t)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrs = append(errs, err)\n\t\t\t\t}\n\t\t\tcase *Tag:\n\t\t\t\terr := p.TagOwners.Contains(t)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrs = append(errs, err)\n\t\t\t\t}\n\t\t\tcase *Username, *Prefix, Asterix:\n\t\t\t\t// User / prefix / wildcard targets are accepted at\n\t\t\t\t// parse time and resolved at compile time (where a\n\t\t\t\t// typo'd username surfaces as a propagated Resolve\n\t\t\t\t// error from compileNodeAttrs). Mirrors the grant\n\t\t\t\t// source-side validation shape.\n\t\t\tdefault:\n\t\t\t\terrs = append(errs, fmt.Errorf(\"%w: %q (%T)\", ErrNodeAttrTargetUnsupported, target, target))\n\t\t\t}\n\t\t}\n\n\t\tfor _, attr := range na.Attrs {\n\t\t\tissue, ok := nodeAttrUnsupportedCaps[attr]\n\t\t\tif ok {\n\t\t\t\terrs = append(errs, fmt.Errorf(\"%w: %q tracked in %s\", ErrNodeAttrUnsupported, attr, issue))\n\t\t\t}\n\t\t}\n\n\t\tif len(na.IPPool) > 0 {\n\t\t\terrs = append(errs, ErrNodeAttrIPPoolUnsupported)\n\t\t}\n\n\t\tfor _, prefix := range na.IPPool {\n\t\t\terr := validateNodeAttrIPPool(prefix)\n\t\t\tif err != nil {\n\t\t\t\terrs = append(errs, err)","sourceCodeStart":2689,"sourceCodeEnd":2725,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L2689-L2725","documentation":"Thrown in Policy.validate()'s nodeAttrs loop (hscontrol/policy/v2/types.go:2707) when a nodeAttrs target is an alias type that has no meaning as a target. Supported: *Host, *AutoGroup, *Tag, *Username, *Prefix, and Asterix (wildcard) — the latter three are deferred to compile time. Any other type lands in the default branch and is reported as ErrNodeAttrTargetUnsupported with the value and its Go type (%T), e.g. a Group used as a nodeAttrs target.","triggerScenarios":"A nodeAttrs entry with \"target\": [\"group:eng\"] — groups are not a supported nodeAttrs target type (they are valid for ACL/SSH/grant sources, which is why the parser produces a *Group alias that then reaches this switch).","commonSituations":"Assuming group: works everywhere autogroup:member does; SaaS policies that headscale's parser types differently than expected; using future/extension alias forms not yet modeled.","solutions":["Replace group:eng with autogroup:member (user-owned devices) or list the usernames explicitly","Use tag:name (registered in tagOwners) to target tagged devices","If you need wildcard coverage, use \"*\" (Asterix), which is accepted at parse time"],"exampleFix":"// before\n\"nodeAttrs\": [{ \"target\": [\"group:eng\"], \"attr\": [\"funnel\"] }]\n\n// after\n\"nodeAttrs\": [{ \"target\": [\"autogroup:member\"], \"attr\": [\"funnel\"] }]","handlingStrategy":"validation","validationCode":"func nodeAttrTargetsSupported(p *policyv2.Policy) error {\n    for _, na := range p.NodeAttrs {\n        for _, t := range na.Targets {\n            switch t.(type) {\n            case *policyv2.Host, *policyv2.AutoGroup, *policyv2.Tag, *policyv2.Username, *policyv2.Prefix, policyv2.Asterix:\n            default:\n                return fmt.Errorf(\"nodeAttrs target type %T unsupported\", t)\n            }\n        }\n    }\n    return nil\n}","typeGuard":"func isNodeAttrTarget(s string) bool {\n    if s == \"*\" { return true }\n    if strings.HasPrefix(s, \"tag:\") { return true }\n    if strings.HasPrefix(s, \"autogroup:\") { return s == \"autogroup:member\" || s == \"autogroup:tagged\" }\n    if _, err := netip.ParsePrefix(s); err == nil { return true }\n    return !strings.ContainsAny(s, \":/\") // bare name: host or username\n}","tryCatchPattern":"if err := pol.Validate(); errors.Is(err, policyv2.ErrNodeAttrTargetUnsupported) { /* replace group:/odd alias with autogroup:member, a username, or a tag */ }","preventionTips":["groups are never valid nodeAttrs targets — use autogroup:member or usernames","The %T in the message reveals exactly which alias type was rejected; use it to find the offending token"],"tags":["policy","nodeattrs","groups","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}