{"record":{"id":"0a03e0ebcdb5ecbb","repo":"juanfont/headscale","slug":"checking-name-uniqueness-w","errorCode":null,"errorMessage":"checking name uniqueness: %w","messagePattern":"checking name uniqueness: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/node.go","lineNumber":197,"sourceCode":"func SetLastSeen(tx *gorm.DB, nodeID types.NodeID, lastSeen time.Time) error {\n\treturn tx.Model(&types.Node{}).Where(\"id = ?\", nodeID).Update(\"last_seen\", lastSeen).Error\n}\n\n// RenameNode takes a [types.Node] struct and a new [types.Node.GivenName] for the nodes\n// and renames it. Validation should be done in the state layer before calling this function.\nfunc RenameNode(tx *gorm.DB,\n\tnodeID types.NodeID, newName string,\n) error {\n\terr := dnsname.ValidLabel(newName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"renaming node: %w\", err)\n\t}\n\n\t// Check if the new name is unique\n\tvar count int64\n\n\tif err := tx.Model(&types.Node{}).Where(\"given_name = ? AND id != ?\", newName, nodeID).Count(&count).Error; err != nil { //nolint:noinlineerr\n\t\treturn fmt.Errorf(\"checking name uniqueness: %w\", err)\n\t}\n\n\tif count > 0 {\n\t\treturn ErrNodeNameNotUnique\n\t}\n\n\tif err := tx.Model(&types.Node{}).Where(\"id = ?\", nodeID).Update(\"given_name\", newName).Error; err != nil { //nolint:noinlineerr\n\t\treturn fmt.Errorf(\"renaming node in database: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc (hsdb *HSDatabase) NodeSetExpiry(nodeID types.NodeID, expiry *time.Time) error {\n\treturn hsdb.Write(func(tx *gorm.DB) error {\n\t\treturn NodeSetExpiry(tx, nodeID, expiry)\n\t})\n}","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/node.go#L179-L215","documentation":"RenameNode counts existing nodes with the target given_name to enforce uniqueness before writing. This error is the COUNT query itself failing at the database level (not a duplicate — that returns ErrNodeNameNotUnique). The underlying driver error is in %w.","triggerScenarios":"SQLite database locked by a concurrent rename/registration; Postgres connection reset; schema drift where given_name column is missing after a botched migration.","commonSituations":"Bulk renames via automation hitting SQLite write contention; interrupted upgrade leaving the schema half-migrated.","solutions":["Unwrap the error to distinguish lock/connectivity/schema causes","For SQLite contention, retry after the competing write completes or enable busy_timeout","Run pending migrations and verify the nodes table schema","Retry the rename once the database is healthy"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := db.RenameNode(tx, nodeID, name); err != nil {\n\tif isTransientDBError(err) {\n\t\t// retry after competing write completes\n\t}\n\treturn err\n}","preventionTips":["Batch renames instead of parallel hammering on SQLite","Keep migrations current so the schema matches queries"],"tags":["go","database","gorm","sqlite","concurrency"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}