{"record":{"id":"47456c1887c0aa80","repo":"juanfont/headscale","slug":"username-must-be-at-least-2-characters-long","errorCode":null,"errorMessage":"username must be at least 2 characters long","messagePattern":"username must be at least 2 characters long","errorType":"validation","errorClass":"util.ErrUsernameTooShort","httpStatus":null,"severity":"warning","filePath":"hscontrol/util/dns.go","lineNumber":30,"sourceCode":"\t\"go4.org/netipx\"\n\t\"tailscale.com/util/dnsname\"\n)\n\nconst (\n\tByteSize          = 8\n\tipv4AddressLength = 32\n\tipv6AddressLength = 128\n\n\t// LabelHostnameLength is the maximum length for a DNS label,\n\t// value related to RFC 1123 and 952.\n\tLabelHostnameLength = 63\n)\n\n// DNS validation errors. Hostname-side validation lives on\n// `tailscale.com/util/dnsname` and [state.NodeStore] collision handling; only\n// the username-side errors stay in this package.\nvar (\n\tErrUsernameTooShort        = errors.New(\"username must be at least 2 characters long\")\n\tErrUsernameMustStartLetter = errors.New(\"username must start with a letter\")\n\tErrUsernameTooManyAt       = errors.New(\"username cannot contain more than one '@'\")\n\tErrUsernameInvalidChar     = errors.New(\"username contains invalid character\")\n)\n\n// ValidateUsername checks if a username is valid.\n// It must be at least 2 characters long, start with a letter, and contain\n// only letters, numbers, hyphens, dots, and underscores.\n// It cannot contain more than one '@'.\n// It cannot contain invalid characters.\nfunc ValidateUsername(username string) error {\n\t// Ensure the username meets the minimum length requirement\n\tif len(username) < 2 {\n\t\treturn ErrUsernameTooShort\n\t}\n\n\t// Ensure the username starts with a letter\n\tif !unicode.IsLetter(rune(username[0])) {","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/util/dns.go#L12-L48","documentation":"ErrUsernameTooShort (hscontrol/util/dns.go:30) is returned by ValidateUsername when the username is shorter than 2 characters. Usernames become MagicDNS labels / identity names, and single- or zero-character names are rejected because they cannot form a valid identity label. It is the first check in ValidateUsername (length, then first-letter, then '@' count, then charset).","triggerScenarios":"Calling util.ValidateUsername with a 0- or 1-character string; reached from user-creation paths (CLI `headscale users create x`, OIDC login mapping a provider sub/email to a name, API user creation).","commonSituations":"Creating a test user with a one-letter name; an OIDC provider returning an unusual claim (e.g. single-char preferred_username); scripted provisioning that derived an empty username from an email local-part.","solutions":["Use a username of at least 2 characters","If the name comes from OIDC, map a different claim or normalize it before it reaches user creation","Pre-check with util.ValidateUsername in your own tooling before calling the API"],"exampleFix":"# before\nheadscale users create a\n\n# after\nheadscale users create alice","handlingStrategy":"validation","validationCode":"if len(username) < 2 {\n\treturn fmt.Errorf(\"username %q too short: %w\", username, util.ErrUsernameTooShort)\n}\nif err := util.ValidateUsername(username); err != nil { // full check\n\treturn err\n}","typeGuard":null,"tryCatchPattern":"if err := util.ValidateUsername(name); err != nil {\n\tif errors.Is(err, util.ErrUsernameTooShort) {\n\t\tname = defaultPrefix + name // pad to >= 2 chars per your convention\n\t} else {\n\t\treturn err\n\t}\n}","preventionTips":["Enforce >=2 chars in provisioning forms before hitting the API","For OIDC, validate the mapped claim before first login creates a user"],"tags":["validation","username","dns","users"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}