{"record":{"id":"fd4967e266aed1af","repo":"juanfont/headscale","slug":"nodeattrs-uses-a-feature-headscale-does-not-yet-su","errorCode":null,"errorMessage":"nodeAttrs uses a feature headscale does not yet support","messagePattern":"nodeAttrs uses a feature headscale does not yet support","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/policy/v2/types.go","lineNumber":91,"sourceCode":"\n// Grant validation errors.\nvar (\n\tErrGrantMissingIPOrApp             = errors.New(\"ip and app can not both be empty\")\n\tErrGrantViaNotATag                 = errors.New(\"via can only be a tag\")\n\tErrProtocolPortInvalidFormat       = errors.New(\"expected only one colon in Internet protocol and port type\")\n\tErrCapNameInvalidForm              = errors.New(\"capability name must have the form {domain}/{path}\")\n\tErrCapNameTailscaleDomain          = errors.New(\"capability name must not be in the tailscale.com domain\")\n\tErrGrantAutogroupSelfInvalidSource = errors.New(\"autogroup:self can only be used with users, groups, or supported autogroups\")\n\tErrGrantAppWithAutogroupInternet   = errors.New(\"cannot use app grants with autogroup:internet\")\n\tErrGrantDefaultRouteCIDR           = errors.New(\"to allow all IP addresses, use \\\"*\\\" or \\\"autogroup:internet\\\"\")\n)\n\n// NodeAttrs validation errors.\nvar (\n\tErrNodeAttrsIPPoolReserved      = errors.New(\"nodeAttrs ipPool must not overlap reserved Tailscale ranges\")\n\tErrNodeAttrsIPPoolOutOfRange    = errors.New(\"nodeAttrs ipPool must be within 100.64.0.0/10\")\n\tErrNodeAttrsAutogroupNotAllowed = errors.New(\"nodeAttrs target does not support this autogroup\")\n\tErrNodeAttrUnsupported          = errors.New(\"nodeAttrs uses a feature headscale does not yet support\")\n\tErrNodeAttrIPPoolUnsupported    = errors.New(\"nodeAttrs ipPool requires the IP allocator (https://github.com/juanfont/headscale/issues/2912)\")\n\tErrNodeAttrTargetUnsupported    = errors.New(\"nodeAttrs target alias type is not supported\")\n)\n\n// nodeAttrUnsupportedCaps lists caps that headscale parses but cannot act on\n// today. Each entry maps to the tracking issue an operator can follow. The\n// caps are accepted by Tailscale SaaS, but delivering them via headscale\n// without the matching server-side machinery would be misleading — nodes\n// would advertise a feature that does not work. Reject at policy load and\n// point operators at the issue.\nvar nodeAttrUnsupportedCaps = map[tailcfg.NodeCapability]string{\n\ttailcfg.NodeAttrFunnel: \"https://github.com/juanfont/headscale/issues/2527\",\n}\n\n// Policy validation errors.\nvar (\n\tErrInvalidUsername             = errors.New(\"username must contain @\")\n\tErrUserNotFound                = errors.New(\"user not found\")","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/policy/v2/types.go#L73-L109","documentation":"Returned at policy validation (hscontrol/policy/v2/types.go:2714) when a nodeAttrs entry uses a capability listed in nodeAttrUnsupportedCaps — currently tailcfg.NodeAttrFunnel. headscale can parse these caps but lacks the server-side machinery to make them work, so delivering them would let nodes advertise a broken feature; the error points at the tracking GitHub issue.","triggerScenarios":"A policy containing nodeAttrs whose attrs array includes \"funnel\" (tailcfg.NodeAttrFunnel). The error message embeds the capability name and its tracking issue URL (currently https://github.com/juanfont/headscale/issues/2527).","commonSituations":"Porting a Tailscale SaaS ACL that enables Funnel via nodeAttrs; enabling funnel in the policy expecting the config flag alone was insufficient; following upstream Tailscale docs that assume the SaaS control plane.","solutions":["Remove the funnel attr from nodeAttrs and enable Funnel via headscale's funnel config/flags instead of policy attrs","Follow the tracking issue in the error message and wait for server-side support","Use plain grants/ACLs for the underlying access; do not rely on Funnel attr until the issue closes"],"exampleFix":"// before\n{\"nodeAttrs\": [{\"target\": [\"autogroup:member\"], \"attrs\": [\"funnel\"]}]}\n// after\n{\"nodeAttrs\": [{\"target\": [\"autogroup:member\"], \"attrs\": []}]}","handlingStrategy":"validation","validationCode":"unsupported := map[string]bool{\"funnel\": true}\nfor _, na := range policy.NodeAttrs {\n    for _, a := range na.Attrs {\n        if unsupported[a] {\n            return fmt.Errorf(\"attr %q unsupported by headscale\", a)\n        }\n    }\n}","typeGuard":"func isSupportedNodeAttr(attr string) bool { return attr != \"funnel\" }","tryCatchPattern":"if errors.Is(err, policy.ErrNodeAttrUnsupported) {\n    // strip the listed attr; the error names the tracking issue\n}","preventionTips":["Do not port Tailscale SaaS funnel nodeAttrs into headscale","Enable Funnel via headscale configuration, not policy attrs","Re-check nodeAttrUnsupportedCaps when upgrading headscale"],"tags":["headscale","policy","nodeattrs","funnel","unsupported-feature"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}