juanfont/headscale · error · ErrNodeNameNotUnique

node name is not unique

Error message

node name is not unique

What it means

ErrNodeNameNotUnique is a sentinel error in hscontrol/state/state.go:78 (mirrored in hscontrol/db/node.go:32) returned when setting a node's GivenName would collide with another node's unique name. It is wrapped with the offending name at state.go:1054 and returned from db uniqueness checks (db/node.go:201); API layers map it to a conflict response (v1/errors.go:32, v2/errors.go:77).

Source

Thrown at hscontrol/state/state.go:78

	// defaultNodeStoreBatchTimeout is the default maximum time to wait before
	// processing a partial batch of node operations.
	defaultNodeStoreBatchTimeout = 500 * time.Millisecond
)

// ErrUnsupportedPolicyMode is returned for invalid policy modes. Valid modes are "file" and "db".
var ErrUnsupportedPolicyMode = errors.New("unsupported policy mode")

// ErrNodeNotFound is returned when a node cannot be found by its ID.
var ErrNodeNotFound = errors.New("node not found")

// ErrInvalidNodeView is returned when an invalid node view is provided.
var ErrInvalidNodeView = errors.New("invalid node view provided")

// ErrNodeNotInNodeStore is returned when a node no longer exists in the [NodeStore].
var ErrNodeNotInNodeStore = errors.New("node no longer exists in NodeStore")

// ErrNodeNameNotUnique is returned when a node name is not unique.
var ErrNodeNameNotUnique = errors.New("node name is not unique")

// nodeUpdateColumns lists all Node columns that should be written
// during a struct-based GORM Updates() call.  Listing them explicitly
// forces GORM to include nil/zero-value fields (e.g. UserID=nil when
// converting a user-owned node to tagged) that struct-based Updates()
// would otherwise silently skip.
//
// Excluded columns:
//   - AuthKeyID, AuthKey: prevents GORM from persisting stale
//     PreAuthKey references after a key has been deleted (#2862).
//   - User: GORM association, not a real column.
//   - IsOnline: runtime-only field (gorm:"-").
//
// Expiry is included here but may be omitted at call sites that must
// not touch it (see persistNodeToDB).
var nodeUpdateColumns = []string{
	"MachineKey",
	"NodeKey",

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Choose a different GivenName that no other node currently uses
  2. Rename or delete the existing node holding the name first
  3. Give cloned machines unique hostnames before they register
  4. Check uniqueness beforehand by listing all nodes and comparing names

Example fix

# before
headscale node rename -i 7 -s node-1   # node-1 already taken

# after
headscale node rename -i 7 -s node-1-b
Defensive patterns

Strategy: validation

Validate before calling

// Before renaming, confirm the target name is unused
for _, n := range listNodes() {
    if n.GivenName == newName && n.ID != targetID {
        return fmt.Errorf("name %q taken", newName)
    }
}

Type guard

null

Try / catch

if errors.Is(err, state.ErrNodeNameNotUnique) { /* prompt for another name or append a suffix */ }

Prevention

When it happens

Trigger: Calling headscale node rename / SetGivenName with a hostname another node already owns; registration flows normalizing a hostname to a value that collides; manually forcing a name that duplicates an existing node.

Common situations: Cloning VMs/containers with identical hostnames so two registrations want the same GivenName; renaming via API without checking the target name is free; name normalization stripping disambiguating suffixes.

Related errors


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