juanfont/headscale · error
renaming node: %w
Error message
renaming node: %w
What it means
RenameNode validates the new GivenName with dnsname.ValidLabel before touching the database. This error means the proposed name is not a valid DNS label: empty, longer than 63 bytes, or containing characters outside the RFC 1035 subset. The comment states validation is expected to live in the state layer, so reaching this DB-layer check means the caller skipped it.
Source
Thrown at hscontrol/db/node.go:190
return hsdb.Write(func(tx *gorm.DB) error {
return SetLastSeen(tx, nodeID, lastSeen)
})
}
// SetLastSeen sets a node's last seen field indicating that we
// have recently communicating with this node.
func SetLastSeen(tx *gorm.DB, nodeID types.NodeID, lastSeen time.Time) error {
return tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("last_seen", lastSeen).Error
}
// RenameNode takes a [types.Node] struct and a new [types.Node.GivenName] for the nodes
// and renames it. Validation should be done in the state layer before calling this function.
func RenameNode(tx *gorm.DB,
nodeID types.NodeID, newName string,
) 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 nilView on GitHub (pinned to 565fd254d0)
Solutions
- Sanitize with dnsname.SanitizeHostname before calling RenameNode
- Reject or truncate names to 63 bytes at the API boundary
- Return a 400-style validation error to the client instead of surfacing the DB error
Example fix
// before
err := db.RenameNode(tx, nodeID, newName) // newName = "my server #1"
// after
name := dnsname.SanitizeHostname(newName)
if name == "" {
return fmt.Errorf("invalid node name: %q", newName)
}
err := db.RenameNode(tx, nodeID, name) Defensive patterns
Strategy: validation
Validate before calling
name := dnsname.SanitizeHostname(newName)
if name == "" || len(name) > 63 {
return fmt.Errorf("rejecting node name %q", newName)
}
err := db.RenameNode(tx, nodeID, name) Type guard
func isValidNodeLabel(s string) bool {
return dnsname.ValidLabel(s) == nil
} Prevention
- Sanitize at the API boundary, not the DB layer
- Trim and length-check user input before rename
- Map this error to a 400 in gRPC handlers
When it happens
Trigger: Passing a raw user-supplied hostname with spaces, dots, or UTF-8 characters; a name of 64+ bytes; an empty string after trimming.
Common situations: gRPC/API rename call with unvalidated input; scripts or automation feeding hostnames straight from cloud instance metadata.
Related errors
- renaming node in database: %w
- unmarshalling dns extra records: %w
- failed to parse ApiKey
- node name is not unique
- node not found
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/296115ed24a14ef6.
Report an issue: GitHub.