juanfont/headscale · error · ErrNodeHasNeitherUserNorTags

node has neither user nor tags - must be owned by user or ta

Error message

node has neither user nor tags - must be owned by user or tagged

What it means

ErrNodeHasNeitherUserNorTags is a sentinel error in hscontrol/state/tags.go:16 returned by validateNodeOwnership when a node has neither a UserID nor any tags. Headscale's ownership model is strict: every node must be either user-owned or tagged, so an ownerless node is rejected.

Source

Thrown at hscontrol/state/tags.go:16

package state

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)
		}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Assign a user or at least one tag to the node before persisting it
  2. When deleting users, delete or reassign their nodes first (db DestroyUser scope handles pre-auth keys; check node handling)
  3. Route node creation through the registration flow so ownership is always set
  4. Repair existing ownerless rows in the DB by setting user_id or tags

Example fix

null
Defensive patterns

Strategy: validation

Validate before calling

func hasOwner(n types.NodeView) bool {
    return n.IsTagged() || n.UserID().Valid()
}

Type guard

null

Try / catch

if errors.Is(err, state.ErrNodeHasNeitherUserNorTags) { /* assign user or tags before persisting */ }

Prevention

When it happens

Trigger: Creating a types.Node with no user and no tags via internal APIs; deleting a node's user without reassigning ownership; registration flows leaving ownership unset before persistence; test fixtures forgetting to set user or tags.

Common situations: User deletion flows that orphan nodes; direct DB manipulation or imports creating ownerless rows; code paths converting user-owned to tagged but clearing the user before tags land.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/af9f773d834c0b02. Report an issue: GitHub.