{"record":{"id":"bf82a713f7516ac5","repo":"juanfont/headscale","slug":"node-name-is-not-unique","errorCode":null,"errorMessage":"node name is not unique","messagePattern":"node name is not unique","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"hscontrol/db/node.go","lineNumber":32,"sourceCode":"\t\"github.com/juanfont/headscale/hscontrol/types\"\n\t\"github.com/juanfont/headscale/hscontrol/util\"\n\t\"github.com/juanfont/headscale/hscontrol/util/zlog/zf\"\n\t\"github.com/rs/zerolog/log\"\n\t\"gorm.io/gorm\"\n\t\"tailscale.com/types/key\"\n\t\"tailscale.com/util/dnsname\"\n)\n\nconst (\n\tNodeGivenNameHashLength = 8\n\tNodeGivenNameTrimSize   = 2\n\n\t// defaultTestNodePrefix is the default hostname prefix for nodes created in tests.\n\tdefaultTestNodePrefix = \"testnode\"\n)\n\n// ErrNodeNameNotUnique is returned when a node name is not unique.\nvar ErrNodeNameNotUnique = errors.New(\"node name is not unique\")\n\n// preloadNode returns a session that eager-loads a node's AuthKey, the\n// AuthKey's User, and the node's User.\nfunc preloadNode(tx *gorm.DB) *gorm.DB {\n\treturn tx.\n\t\tPreload(\"AuthKey\").\n\t\tPreload(\"AuthKey.User\").\n\t\tPreload(\"User\")\n}\n\nvar (\n\tErrNodeNotFound                  = errors.New(\"node not found\")\n\tErrNodeRouteIsNotAvailable       = errors.New(\"route is not available on node\")\n\tErrNodeNotFoundRegistrationCache = errors.New(\n\t\t\"node not found in registration cache\",\n\t)\n\tErrCouldNotConvertNodeInterface = errors.New(\"failed to convert node interface\")\n)","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/node.go#L14-L50","documentation":"Sentinel in hscontrol/db/node.go returned by RenameNode (node.go:200-201) and by the state layer (hscontrol/state/state.go:1054) when a node's given_name is already taken by another node. Given names are DNS labels under MagicDNS, so headscale enforces uniqueness before writing. API error mappers (hscontrol/api/v1/errors.go:32, v2/errors.go:77) map it to a conflict response.","triggerScenarios":"Calling `headscale nodes rename` (or the gRPC/API rename) with a name that another node already has as its given_name; concurrent registration of two nodes that normalize to the same given name.","commonSituations":"Renaming node B to node A's name to 'migrate' it; VM clones re-registering with identical hostnames so the generated given names collide during manual rename.","solutions":["Pick a different name, or first rename/release the node currently holding it","List nodes (`headscale nodes list`) to see existing given names before choosing","Delete the stale node occupying the name if it is decommissioned"],"exampleFix":"# before\nheadscale nodes rename --identifier 7 --new-name laptop\n# error: node name is not unique\n\n# after\nheadscale nodes rename --identifier 7 --new-name laptop-2","handlingStrategy":"validation","validationCode":"// before renaming, check uniqueness the same way the DB does\ncount, err := tx.Model(&types.Node{}).\n    Where(\"given_name = ? AND id != ?\", newName, nodeID).Count(&new(int64)).Error","typeGuard":null,"tryCatchPattern":"if err := state.RenameNode(nodeID, newName); err != nil {\n    if errors.Is(err, state.ErrNodeNameNotUnique) {\n        // map to a 409-style conflict for API callers\n        return conflict(\"given_name already in use: \" + newName)\n    }\n    return err\n}","preventionTips":["Derive names deterministically and include a unique suffix when auto-naming","Have API clients treat name conflicts as retryable-with-new-name, not as a server error","Check `headscale nodes list` output in provisioning scripts before assigning a name"],"tags":["node","naming","dns","conflict","headscale"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}