juanfont/headscale · error · ErrNodeMarkedTaggedButHasNoTags
node marked as tagged but has no tags
Error message
node marked as tagged but has no tags
What it means
ErrNodeMarkedTaggedButHasNoTags is a sentinel error in hscontrol/state/tags.go:13 returned by validateNodeOwnership when a node's IsTagged() is true but its Tags slice is empty. It guards the tags-XOR-user-ownership invariant: a tagged node must carry at least one tag.
Source
Thrown at hscontrol/state/tags.go:13
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() {View on GitHub (pinned to 565fd254d0)
Solutions
- Ensure the node gets at least one valid tag before it is marked tagged (use state.SetTags, not direct field writes)
- If the intent is a user-owned node, clear the tagged marker instead of emptying Tags
- Fix test fixtures to include a tag when exercising tagged-node paths
- Audit direct DB writes / import scripts that bypass ownership validation
Example fix
// before
node := types.Node{MachineKey: mk, NodeKey: nk} // tagged marker set, Tags empty
// after
// go through state.SetTags(node, []string{"tag:web"}) so ownership stays consistent Defensive patterns
Strategy: validation
Validate before calling
func hasValidOwnership(n types.NodeView) error {
if n.IsTagged() && len(n.Tags()) == 0 {
return errors.New("tagged node missing tags")
}
return nil
} Type guard
null
Try / catch
if errors.Is(err, state.ErrNodeMarkedTaggedButHasNoTags) { /* set tags or clear the tagged marker */ } Prevention
- Always mutate tags through state.SetTags rather than direct field/DB writes
- In tests, build tagged fixtures with a real tag value
When it happens
Trigger: Constructing or persisting a types.Node with a tagged owner marker but an empty Tags list; SetTags clearing tags without clearing the tagged marker; tests building node fixtures that set the tagged flag without tags.
Common situations: Manual construction of Node structs in code or tests bypassing SetTags validation; partial DB updates that empty the tags column but leave the ownership marker; import scripts writing nodes directly.
Related errors
- tag must start with 'tag:'
- tag not found
- machine key maps to ambiguous node ownership
- node has neither user nor tags - must be owned by user or ta
- failed to parse ApiKey
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/b078bb5cdefeb972.
Report an issue: GitHub.