juanfont/headscale · error · ErrGivenNameTaken

given name already in use by another node

Error message

given name already in use by another node

What it means

ErrGivenNameTaken is returned by NodeStore.SetGivenName (hscontrol/state/node_store.go:507) when renaming a node to a DNS label that another node in the current snapshot already holds (case-sensitive GivenName equality across all other node IDs). Given names must be unique per tailnet for MagicDNS to resolve unambiguously; unlike registration-time name clashes, renames never auto-suffix. State.RenameNode (state.go:1053) rewraps it as ErrNodeNameNotUnique for API/CLI callers.

Source

Thrown at hscontrol/state/node_store.go:32

	"github.com/juanfont/headscale/hscontrol/types"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"tailscale.com/net/tsaddr"
	"tailscale.com/types/key"
	"tailscale.com/types/views"
	"tailscale.com/util/dnsname"
)

// fallbackGivenName is the DNS label used when a node is written with
// an empty [types.Node.GivenName]. Matches Tailscale SaaS behaviour
// for empty sanitised labels.
const fallbackGivenName = "node"

// Errors returned by [NodeStore.SetGivenName]. [ErrNodeNotFound] is defined
// in state.go and reused here.
var (
	ErrGivenNameTaken   = errors.New("given name already in use by another node")
	ErrGivenNameInvalid = errors.New("given name is not a valid DNS label")
)

const (
	put             = 1
	del             = 2
	rebuildPeerMaps = 4
	setName         = 5
	updateMulti     = 6
)

const prometheusNamespace = "headscale"

var (
	nodeStoreOperations = promauto.NewCounterVec(prometheus.CounterOpts{
		Namespace: prometheusNamespace,
		Name:      "nodestore_operations_total",
		Help:      "Total number of NodeStore operations",

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Pick a different, unused DNS label (list nodes first: headscale node list to see current names)
  2. Or rename/free the node currently holding the label first, then retry
  3. Catch ErrNodeNameNotUnique in callers and surface a user-facing 'name taken' message rather than retrying
  4. Remember comparison is exact — 'Laptop' and 'laptop' differ; check case

Example fix

// before
headscale node rename -i 2 laptop   // node 1 already is "laptop"
// after
headscale node list                 // verify taken names
headscale node rename -i 2 laptop2
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check name availability before renaming
func nameFree(store *state.NodeStore, id types.NodeID, name string) bool {
    for _, n := range store.Snapshot().Nodes() { // any read-only node listing
        if n.ID() != id && n.GivenName() == name { return false }
    }
    return true
}

Try / catch

view, change, err := st.RenameNode(nodeID, newName)
if err != nil {
    if errors.Is(err, state.ErrNodeNameNotUnique) { // wraps ErrGivenNameTaken
        // prompt the admin for a different label; do NOT retry unchanged
    }
    if errors.Is(err, state.ErrGivenNameInvalid) {
        // fix the label: must be a valid DNS label fitting the FQDN limit
    }
}

Prevention

When it happens

Trigger: State.RenameNode(nodeID, name) / headscale node rename CLI where any other node's GivenName equals the requested label exactly (node_store.go:499-504 scans all nodes, skipping the node's own ID). Renaming a node to its own current name is idempotent and allowed.

Common situations: Two machines registered with the same hostname (the second usually got an auto-suffixed name) and an admin renaming one onto the other's label; renaming to a name later taken by a newly registered node; retrying a rename after partial failures without checking current state.

Related errors


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