{"record":{"id":"8a2f5450e2c3ba40","repo":"juanfont/headscale","slug":"using-pre-auth-key-w","errorCode":null,"errorMessage":"using pre auth key: %w","messagePattern":"using pre auth key: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/state/state.go","lineNumber":2033,"sourceCode":"\n\t// Seed GivenName from the sanitised raw hostname. [NodeStore.PutNode]\n\t// bumps on collision and falls back to \"node\" if the sanitised\n\t// result is empty (pure non-ASCII / punctuation input).\n\tif nodeToRegister.GivenName == \"\" {\n\t\tnodeToRegister.GivenName = dnsname.SanitizeHostname(nodeToRegister.Hostname)\n\t}\n\n\t// New node - database first to get ID, then [NodeStore]\n\tsavedNode, err := hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) {\n\t\terr := tx.Save(&nodeToRegister).Error\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"saving node: %w\", err)\n\t\t}\n\n\t\tif params.PreAuthKey != nil && !params.PreAuthKey.Reusable {\n\t\t\terr := hsdb.UsePreAuthKey(tx, params.PreAuthKey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"using pre auth key: %w\", err)\n\t\t\t}\n\t\t}\n\n\t\treturn &nodeToRegister, nil\n\t})\n\tif err != nil {\n\t\treturn types.NodeView{}, err\n\t}\n\n\t// Add to [NodeStore] after database creates the ID\n\treturn s.nodeStore.PutNode(*savedNode), nil\n}\n\n// validateRequestTags validates that the requested tags are permitted for the node.\n// This should be called BEFORE [NodeStore.UpdateNode] to ensure we don't modify [NodeStore]\n// if validation fails. Returns the list of rejected tags (empty if all valid).\nfunc (s *State) validateRequestTags(node types.NodeView, requestTags []string) []string {\n\t// Empty tags = clear tags, always permitted","sourceCodeStart":2015,"sourceCodeEnd":2051,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/state/state.go#L2015-L2051","documentation":"Marking a non-reusable pre-auth key as used failed inside the new-node registration transaction, right after the node INSERT succeeded. hsdb.UsePreAuthKey performs an atomic compare-and-set on the key's Used flag; the whole transaction (including the just-inserted node) rolls back on failure.","triggerScenarios":"Registering with a one-shot (non-reusable) key where UsePreAuthKey fails: the key was already used (concurrent registration with the same key), the key expired between validation and write, or the row was deleted mid-transaction.","commonSituations":"Two containers boot simultaneously sharing one single-use auth key; key expired during a long OIDC/interactive delay; key deleted by an admin while a client was mid-registration.","solutions":["If the key is legitimately shared by multiple hosts, recreate it with --reusable","Generate a fresh key if this one is used up or expired (`headscale preauthkeys list` shows state)","Serialize container startups so one-shot keys are consumed one at a time"],"exampleFix":"# before\nheadscale preauthkeys create --user alice --expiration 1h\n\n# after (shared across N hosts)\nheadscale preauthkeys create --user alice --reusable --expiration 1h","handlingStrategy":"validation","validationCode":"// Provisioning-time key audit before handing the key to a client:\nif key.Used && !key.Reusable {\n    return fmt.Errorf(\"key %d already consumed; create --reusable or a new key\", key.ID)\n}\nif key.Expiration != nil && key.Expiration.Before(time.Now()) {\n    return errors.New(\"key expired; generate a new one\")\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"using pre auth key\") {\n    // Whole tx rolled back; mint a fresh key rather than retrying\n    key, _ = client.CreatePreAuthKey(user, true /*reusable*/, ttl)\n}","preventionTips":["Use --reusable for any key consumed by more than one process or restart cycle","Never share a single-use key across container replicas","Check `headscale preauthkeys list` for used/expired state before debugging registrations"],"tags":["preauthkey","registration","database","go"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}