{"record":{"id":"4f36a29f2fb28338","repo":"juanfont/headscale","slug":"username-must-start-with-a-letter","errorCode":null,"errorMessage":"username must start with a letter","messagePattern":"username must start with a letter","errorType":"validation","errorClass":"util.ErrUsernameMustStartLetter","httpStatus":null,"severity":"warning","filePath":"hscontrol/util/dns.go","lineNumber":31,"sourceCode":"\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])) {\n\t\treturn ErrUsernameMustStartLetter","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/util/dns.go#L13-L49","documentation":"ErrUsernameMustStartLetter (hscontrol/util/dns.go:31) is returned by ValidateUsername when the first character of the username is not an ASCII letter. This mirrors DNS-label and classic username rules (RFC 1123/952 heritage referenced by the constants in dns.go): names must start with a letter, then may contain letters, digits, hyphens, dots, underscores, and one '@'.","triggerScenarios":"Calling util.ValidateUsername with names like \"1alice\", \"_bob\", \"-carol\", or \".dave\" — any leading non-letter. Reached from CLI user creation, API user creation, and OIDC-derived usernames.","commonSituations":"Provisioning machine-generated usernames that begin with a digit (e.g. student ID \"123456\"); email local-parts starting with a dot or digit; import scripts from systems with laxer naming rules.","solutions":["Prefix or rename so the name starts with a letter (e.g. u123456 instead of 123456)","For OIDC, transform the incoming claim (prefix with a letter) before user creation","Run util.ValidateUsername in provisioning scripts to fail before the API call"],"exampleFix":"# before\nheadscale users create 1alice\n\n# after\nheadscale users create alice1","handlingStrategy":"validation","validationCode":"func startsWithLetter(s string) bool {\n\treturn len(s) > 0 && ((s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z'))\n}\nif !startsWithLetter(username) {\n\treturn fmt.Errorf(\"username %q must start with a letter\", username)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefix digit-leading generated names with a letter (u123456)","Validate via util.ValidateUsername before calling user-creation APIs"],"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"}