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

  1. Add the tag to policy with a tagOwners entry granting it to the requesting node's owner (e.g. the user or a group)
  2. Verify the exact tag string matches between the node's --advertise-tags and the policy
  3. Reload the policy after editing (policy mode file: fix and save; db: update via API)
  4. 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

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.