juanfont/headscale · error · ErrRequestedTagsInvalidOrNotPermitted
requested tags
Error message
requested tags
What it means
ErrRequestedTagsInvalidOrNotPermitted is a sentinel in hscontrol/state/tags.go:20 whose short message is deliberately wrapped with detail to match the Tailscale SaaS format "requested tags [tag:xxx] are invalid or not permitted". It is returned when a node requests tags that are malformed or whose owning policy does not permit the requesting node (tag owners rule in policy v2).
Source
Thrown at hscontrol/state/tags.go:20
import (
"errors"
"fmt"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/rs/zerolog/log"
)
var (
// ErrNodeMarkedTaggedButHasNoTags is returned when a node is marked as tagged but has no tags.
ErrNodeMarkedTaggedButHasNoTags = errors.New("node marked as tagged but has no tags")
// ErrNodeHasNeitherUserNorTags is returned when a node has neither a user nor tags.
ErrNodeHasNeitherUserNorTags = errors.New("node has neither user nor tags - must be owned by user or tagged")
// ErrRequestedTagsInvalidOrNotPermitted is returned when requested tags are invalid or not permitted.
// This message format matches Tailscale SaaS: "requested tags [tag:xxx] are invalid or not permitted".
ErrRequestedTagsInvalidOrNotPermitted = errors.New("requested tags")
)
// ErrTaggedNodeHasUser is returned when a tagged node has a [types.Node.UserID] set.
var ErrTaggedNodeHasUser = errors.New("tagged node must not have user_id set")
// validateNodeOwnership ensures proper node ownership model.
// A node must be either user-owned or tagged, and these are mutually exclusive:
// tagged nodes must not have a [types.Node.UserID], and user-owned nodes must
// not have tags.
func validateNodeOwnership(node *types.Node) error {
if node.IsTagged() {
if len(node.Tags) == 0 {
return fmt.Errorf("%w: %q", ErrNodeMarkedTaggedButHasNoTags, node.Hostname)
}
if node.UserID != nil {
return fmt.Errorf("%w: %q", ErrTaggedNodeHasUser, node.Hostname)
}View on GitHub (pinned to 565fd254d0)
Solutions
- Add the tag to policy with a tagOwners entry granting it to the requesting node's owner (e.g. the user or a group)
- Verify the exact tag string matches between the node's --advertise-tags and the policy
- Reload the policy after editing (policy mode file: fix and save; db: update via API)
- If the tag is not intended, remove it from the node's advertised tags
Example fix
// before: policy has no owner for tag:prod, node advertises tag:prod
"tagOwners": { "tag:server": ["group:admin"] }
// after
"tagOwners": { "tag:server": ["group:admin"], "tag:prod": ["group:admin"] } Defensive patterns
Strategy: validation
Validate before calling
// Before advertising a tag, confirm the policy grants it to the node's owner
owners := policy.TagOwners()["tag:prod"]
if !owners.Contains(nodeOwner) { return errors.New("tag not permitted") } Type guard
null
Try / catch
if errors.Is(err, state.ErrRequestedTagsInvalidOrNotPermitted) { /* fix tagOwners in policy, reload, re-register */ } Prevention
- Add tagOwners entries before rolling out --advertise-tags to nodes
- Keep tag names consistent between node config and policy; reload policy after edits
When it happens
Trigger: A node registers with Hostinfo request tags like tag:prod but the policy's tagOwners section does not grant that tag to the node's user/IPs; requesting a syntactically invalid tag; changing a pre-auth key or user so previously-granted tags are no longer owned.
Common situations: Policy file missing a tagOwners entry for a tag nodes advertise; adding request tags in tailscale_up ('--advertise-tags=tag:x') without updating the ACL policy first; typos in tag names between node config and policy.
Related errors
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/b8a6b2c6493ce740.
Report an issue: GitHub.