{"record":{"id":"468e45b7ba041e8a","repo":"juanfont/headscale","slug":"registering-existing-node-in-database-w","errorCode":null,"errorMessage":"registering existing node in database: %w","messagePattern":"registering existing node in database: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/node.go","lineNumber":310,"sourceCode":"\t\tnode.GivenName = oldNode.GivenName\n\t\tnode.ApprovedRoutes = oldNode.ApprovedRoutes\n\t\t// Don't overwrite the provided IPs with old ones when they exist\n\t\tif ipv4 == nil {\n\t\t\tipv4 = oldNode.IPv4\n\t\t}\n\n\t\tif ipv6 == nil {\n\t\t\tipv6 = oldNode.IPv6\n\t\t}\n\t}\n\n\t// If the node exists and it already has IP(s), we just save it\n\t// so we store the node.Expire and node.Nodekey that has been set when\n\t// adding it to the registrationCache\n\tif node.IPv4 != nil || node.IPv6 != nil {\n\t\terr := tx.Save(&node).Error\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"registering existing node in database: %w\", err)\n\t\t}\n\n\t\tlog.Trace().\n\t\t\tCaller().\n\t\t\tStr(zf.NodeHostname, node.Hostname).\n\t\t\tStr(zf.MachineKey, node.MachineKey.ShortString()).\n\t\t\tStr(zf.NodeKey, node.NodeKey.ShortString()).\n\t\t\tStr(zf.UserName, node.User.Username()).\n\t\t\tMsg(\"Test node authorized again\")\n\n\t\treturn &node, nil\n\t}\n\n\tnode.IPv4 = ipv4\n\tnode.IPv6 = ipv6\n\n\tif node.GivenName == \"\" {\n\t\tnode.GivenName = dnsname.SanitizeHostname(node.Hostname)","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/node.go#L292-L328","documentation":"When a node re-registers and already holds at least one IP, RegisterNode takes the fast path of saving the refreshed node (expiry, node key from the registration cache). This error is that tx.Save failing — typically a unique-constraint hit on machine key/node key or a database availability problem.","triggerScenarios":"Concurrent re-registration of the same machine from two clients; node key colliding with another row after key rotation; DB lock or connection loss during registration.","commonSituations":"Client retry during network flakiness causing parallel registration attempts; clock skew or restored DB producing key conflicts.","solutions":["Unwrap the error; unique violations point to concurrent registration — serialize per machine key","For lock/connectivity causes, address SQLite contention or Postgres health and let the client retry registration","Inspect the nodes table for conflicting machine_key/node_key rows"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"node, err := db.RegisterNode(tx, node)\nif err != nil {\n\tif isUniqueViolation(err) {\n\t\t// concurrent registration of same machine; reload and take existing path\n\t}\n\treturn nil, err\n}","preventionTips":["Key registration work by machine key so the same machine serializes","Let clients retry registration on 5xx","Monitor for machine_key/node_key duplicates after restores"],"tags":["go","database","registration","gorm","concurrency"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}