{"record":{"id":"362ec11d28260de1","repo":"juanfont/headscale","slug":"handling-register-with-auth-key-w","errorCode":null,"errorMessage":"handling register with auth key: %w","messagePattern":"handling register with auth key: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/auth.go","lineNumber":141,"sourceCode":"\t// If the [tailcfg.RegisterRequest] has a Followup URL, it means that the\n\t// node has already started the registration process and we should wait for\n\t// it to finish the original registration.\n\tif req.Followup != \"\" {\n\t\treturn h.waitForFollowup(ctx, req, machineKey)\n\t}\n\n\t// Pre authenticated keys are handled slightly different than interactive\n\t// logins as they can be done fully sync and we can respond to the node with\n\t// the result as it is waiting.\n\tif isAuthKey(req) {\n\t\tresp, err := h.handleRegisterWithAuthKey(req, machineKey)\n\t\tif err != nil {\n\t\t\t// Preserve HTTPError types so they can be handled properly by the HTTP layer\n\t\t\tif httpErr, ok := errors.AsType[HTTPError](err); ok {\n\t\t\t\treturn nil, httpErr\n\t\t\t}\n\n\t\t\treturn nil, fmt.Errorf(\"handling register with auth key: %w\", err)\n\t\t}\n\n\t\treturn resp, nil\n\t}\n\n\tresp, err := h.handleRegisterInteractive(req, machineKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"handling register interactive: %w\", err)\n\t}\n\n\treturn resp, nil\n}\n\n// handleLogout checks if the [tailcfg.RegisterRequest] is a\n// logout attempt from a node. If the node is not attempting to.\nfunc (h *Headscale) handleLogout(\n\tnode types.NodeView,\n\treq tailcfg.RegisterRequest,","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/auth.go#L123-L159","documentation":"Wraps failures of handleRegisterWithAuthKey during registration with a pre-auth key (hscontrol/auth.go:141). Note the special passthrough: if the inner error is an HTTPError it is returned unwrapped so the HTTP layer maps it to a status code; this generic wrap only applies to non-HTTP errors such as state/DB failures. Business-rule failures (invalid, expired, over-used, or reusable-misused auth keys) surface as HTTPError instead.","triggerScenarios":"`tailscale up --authkey=<key>` while the DB write of the new node fails (lock, connection loss); state-layer errors while attaching the node to the key's user; policy evaluation errors during pre-auth registration. Distinct from the HTTPError cases: malformed key string, expired key, single-use key already consumed, or user mismatch.","commonSituations":"Automated node provisioning (Terraform/Ansible/containers) hitting DB contention when many nodes register in parallel; DB briefly unavailable during bulk enrollment; ephemeral test nodes registering faster than SQLite can commit.","solutions":["Check logs for the wrapped non-HTTP cause — usually a database error — and fix that first (locks, connectivity, pool size).","Retry `tailscale up --authkey=...` after the DB issue clears; registration with the same key is safe unless it was single-use and already consumed.","If the error is actually an HTTPError (expired/used key), create a new auth key: `headscale preauthkeys create ...` and retry.","For bulk parallel enrollments, stagger registrations or move to PostgreSQL to avoid SQLite write locking."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"// HTTPError is passed through unwrapped; use it to distinguish key problems from DB problems.\nif httpErr, ok := errors.AsType[HTTPError](err); ok {\n    // auth-key policy failure (expired/used/invalid): surface to user, create a new key\n} else {\n    // wrapped 'handling register with auth key': infrastructure/DB failure, retry\n}","tryCatchPattern":"resp, err := h.handleRegister(req, mk)\nif err != nil {\n    if httpErr, ok := errors.AsType[HTTPError](err); ok {\n        return nil, httpErr // maps to an HTTP status for the client\n    }\n    // retryable infra failure: log, back off, retry registration\n}","preventionTips":["Check auth-key validity (expiry, use count) before mass enrollment.","Stagger automated registrations to avoid SQLite write storms.","Distinguish HTTPError (user-fixable) from wrapped errors (infra-fixable) in automation."],"tags":["authentication","preauth-key","registration","database"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}