juanfont/headscale · error

saving node to database

Error message

saving node to database: %w

What it means

The save for a node taking the fresh-registration path (it had no existing IPs): after assigning ipv4/ipv6 and defaulting GivenName, tx.Save persists the new node row. Failure means the INSERT/UPDATE was rejected — unique constraint on machine key, node key, or given name, or a database availability error.

Solutions

  1. Unwrap the driver error to identify the violated constraint
  2. Ensure the user record exists and is committed before registering the node
  3. For duplicate-machine-key races, retry registration so the existing-node path handles it
  4. Fix underlying DB health if the error is lock/connectivity
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the owning user row is committed before registering
if _, err := db.GetUserByID(uid); err != nil {
	return nil, fmt.Errorf("cannot register node for missing user: %w", err)
}

Try / catch

if _, err := db.RegisterNode(tx, node); err != nil {
	if isUniqueViolation(err) || isTransientDBError(err) {
		// retryable: race or lock
	}
	return err
}

Prevention

When it happens

Trigger: Same machine registering twice concurrently; expired node re-registering with a key that now duplicates another row; DB lock/timeout; NOT NULL or foreign-key violation (e.g. user id not yet committed).

Common situations: Race between interactive auth callback and client poll; automation pre-creating rows; partial migration leaving constraints inconsistent.

Related errors


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

Appendix: source

Thrown at hscontrol/db/node.go:335

			Str(zf.NodeKey, node.NodeKey.ShortString()).
			Str(zf.UserName, node.User.Username()).
			Msg("Test node authorized again")

		return &node, nil
	}

	node.IPv4 = ipv4
	node.IPv6 = ipv6

	if node.GivenName == "" {
		node.GivenName = dnsname.SanitizeHostname(node.Hostname)
		if node.GivenName == "" {
			node.GivenName = "node"
		}
	}

	if err := tx.Save(&node).Error; err != nil { //nolint:noinlineerr
		return nil, fmt.Errorf("saving node to database: %w", err)
	}

	log.Trace().
		Caller().
		Str(zf.NodeHostname, node.Hostname).
		Msg("Test node registered with the database")

	return &node, nil
}

// NodeSetNodeKey sets the node key of a node and saves it to the database.
func NodeSetNodeKey(tx *gorm.DB, node *types.Node, nodeKey key.NodePublic) error {
	return tx.Model(node).Updates(types.Node{
		NodeKey: nodeKey,
	}).Error
}

func (hsdb *HSDatabase) NodeSetMachineKey(

View on GitHub (pinned to 565fd254d0)