{"record":{"id":"0aa5a900f1e5303d","repo":"juanfont/headscale","slug":"given-name-already-in-use-by-another-node","errorCode":null,"errorMessage":"given name already in use by another node","messagePattern":"given name already in use by another node","errorType":"http","errorClass":"ErrGivenNameTaken","httpStatus":400,"severity":"error","filePath":"hscontrol/state/node_store.go","lineNumber":32,"sourceCode":"\n\t\"github.com/juanfont/headscale/hscontrol/types\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promauto\"\n\t\"tailscale.com/net/tsaddr\"\n\t\"tailscale.com/types/key\"\n\t\"tailscale.com/types/views\"\n\t\"tailscale.com/util/dnsname\"\n)\n\n// fallbackGivenName is the DNS label used when a node is written with\n// an empty [types.Node.GivenName]. Matches Tailscale SaaS behaviour\n// for empty sanitised labels.\nconst fallbackGivenName = \"node\"\n\n// Errors returned by [NodeStore.SetGivenName]. [ErrNodeNotFound] is defined\n// in state.go and reused here.\nvar (\n\tErrGivenNameTaken   = errors.New(\"given name already in use by another node\")\n\tErrGivenNameInvalid = errors.New(\"given name is not a valid DNS label\")\n)\n\nconst (\n\tput             = 1\n\tdel             = 2\n\trebuildPeerMaps = 4\n\tsetName         = 5\n\tupdateMulti     = 6\n)\n\nconst prometheusNamespace = \"headscale\"\n\nvar (\n\tnodeStoreOperations = promauto.NewCounterVec(prometheus.CounterOpts{\n\t\tNamespace: prometheusNamespace,\n\t\tName:      \"nodestore_operations_total\",\n\t\tHelp:      \"Total number of NodeStore operations\",","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/state/node_store.go#L14-L50","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pick a different, unused DNS label (list nodes first: headscale node list to see current names)","Or rename/free the node currently holding the label first, then retry","Catch ErrNodeNameNotUnique in callers and surface a user-facing 'name taken' message rather than retrying","Remember comparison is exact — 'Laptop' and 'laptop' differ; check case"],"exampleFix":"// before\nheadscale node rename -i 2 laptop   // node 1 already is \"laptop\"\n// after\nheadscale node list                 // verify taken names\nheadscale node rename -i 2 laptop2","handlingStrategy":"try-catch","validationCode":"// Pre-check name availability before renaming\nfunc nameFree(store *state.NodeStore, id types.NodeID, name string) bool {\n    for _, n := range store.Snapshot().Nodes() { // any read-only node listing\n        if n.ID() != id && n.GivenName() == name { return false }\n    }\n    return true\n}","typeGuard":null,"tryCatchPattern":"view, change, err := st.RenameNode(nodeID, newName)\nif err != nil {\n    if errors.Is(err, state.ErrNodeNameNotUnique) { // wraps ErrGivenNameTaken\n        // prompt the admin for a different label; do NOT retry unchanged\n    }\n    if errors.Is(err, state.ErrGivenNameInvalid) {\n        // fix the label: must be a valid DNS label fitting the FQDN limit\n    }\n}","preventionTips":["List node names (headscale node list) before choosing a new label","Remember names are per-tailnet unique and compared exactly (case-sensitive)","Rename is idempotent for a node's own current name but never auto-suffixes on collision","Surface name-taken errors to the admin UI instead of retrying automatically"],"tags":["state","dns","rename","node","uniqueness"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}