juanfont/headscale · error

renaming node in database: %w

Error message

renaming node in database: %w

What it means

The final step of RenameNode: an UPDATE of given_name for the node id. Failure is a database-level error — uniqueness race (another node took the name between the COUNT and UPDATE), lock, or the node id no longer existing. The uniqueness pre-check and update are not in a serializable snapshot, so a rare race can surface a constraint violation here.

Source

Thrown at hscontrol/db/node.go:205

) error {
	err := dnsname.ValidLabel(newName)
	if err != nil {
		return fmt.Errorf("renaming node: %w", err)
	}

	// Check if the new name is unique
	var count int64

	if err := tx.Model(&types.Node{}).Where("given_name = ? AND id != ?", newName, nodeID).Count(&count).Error; err != nil { //nolint:noinlineerr
		return fmt.Errorf("checking name uniqueness: %w", err)
	}

	if count > 0 {
		return ErrNodeNameNotUnique
	}

	if err := tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("given_name", newName).Error; err != nil { //nolint:noinlineerr
		return fmt.Errorf("renaming node in database: %w", err)
	}

	return nil
}

func (hsdb *HSDatabase) NodeSetExpiry(nodeID types.NodeID, expiry *time.Time) error {
	return hsdb.Write(func(tx *gorm.DB) error {
		return NodeSetExpiry(tx, nodeID, expiry)
	})
}

// NodeSetExpiry sets a new expiry time for a node.
// If expiry is nil, the node's expiry is disabled (node will never expire).
func NodeSetExpiry(tx *gorm.DB, nodeID types.NodeID, expiry *time.Time) error {
	return tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("expiry", expiry).Error
}

func (hsdb *HSDatabase) DeleteNode(node *types.Node) error {

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Unwrap to check for a unique-constraint violation and map it to ErrNodeNameNotUnique semantics for the client
  2. Serialize renames (single writer) or retry the whole rename on constraint races
  3. Verify the node still exists before retrying
Defensive patterns

Strategy: try-catch

Try / catch

if err := db.RenameNode(tx, nodeID, name); err != nil {
	if errors.Is(err, db.ErrNodeNameNotUnique) || isUniqueViolation(err) {
		return ErrNameTaken // surface as 409
	}
	return err
}

Prevention

When it happens

Trigger: Two concurrent renames to the same name racing past the COUNT check; node deleted between check and update; DB lock/timeout.

Common situations: Automation renaming many nodes in parallel; API retry storms.

Related errors


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