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
- Assign a user or at least one tag to the node before persisting it
- When deleting users, delete or reassign their nodes first (db DestroyUser scope handles pre-auth keys; check node handling)
- Route node creation through the registration flow so ownership is always set
- 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 deleting users, delete or reassign their nodes first
- Never persist ownerless Node structs; go through registration flows
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
- machine key maps to ambiguous node ownership
- node marked as tagged but has no tags
- failed to parse ApiKey
- foreign key constraints violated
- failed to parse oauth client secret
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/af9f773d834c0b02.
Report an issue: GitHub.