{"record":{"id":"fd6120e84148c83e","repo":"juanfont/headscale","slug":"handling-existing-node-w","errorCode":null,"errorMessage":"handling existing node: %w","messagePattern":"handling existing node: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/auth.go","lineNumber":103,"sourceCode":"\t\t\t// the Noise session's machine key matches the cached node.\n\t\t\t// Without this check anyone holding a target's NodeKey could\n\t\t\t// open a Noise session with a throwaway machine key and read\n\t\t\t// the owner's User/Login back through [nodeToRegisterResponse].\n\t\t\t// [Headscale.handleLogout] enforces the same check on its own path.\n\t\t\terr := machineKeyMismatch(node, machineKey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// When tailscaled restarts, it sends [tailcfg.RegisterRequest] with Auth=nil and Expiry=zero.\n\t\t\t// Return the current node state without modification.\n\t\t\tif req.Expiry.IsZero() && !node.IsExpired() {\n\t\t\t\treturn nodeToRegisterResponse(node), nil\n\t\t\t}\n\n\t\t\tresp, err := h.handleLogout(node, req, machineKey)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"handling existing node: %w\", err)\n\t\t\t}\n\n\t\t\t// If resp is not nil, we have a response to return to the node.\n\t\t\t// If resp is nil, we should proceed and see if the node is trying to re-auth.\n\t\t\tif resp != nil {\n\t\t\t\treturn resp, nil\n\t\t\t}\n\t\t} else {\n\t\t\t// If the register request is not attempting to register a node, and\n\t\t\t// we cannot match it with an existing node, we consider that unexpected\n\t\t\t// as only register nodes should attempt to log out.\n\t\t\tlog.Debug().\n\t\t\t\tStr(\"node.key\", req.NodeKey.ShortString()).\n\t\t\t\tStr(\"machine.key\", machineKey.ShortString()).\n\t\t\t\tBool(\"unexpected\", true).\n\t\t\t\tMsg(\"received register request with no auth, and no existing node\")\n\t\t}\n\t}","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/auth.go#L85-L121","documentation":"Wraps a failure of handleLogout when called on an existing node during registration (hscontrol/auth.go:103). This branch handles a RegisterRequest with Auth=nil and a non-zero Expiry for a node already known by node key; handleLogout either deletes an ephemeral node or sets its expiry to now. Any state/DB error from DeleteNode or SetNodeExpiry is re-wrapped with this message.","triggerScenarios":"tailscaled sends a register request with an expiry (logout) for an existing node while the database errors: ephemeral deletion fails (FK/constraint/lock) or SetNodeExpiry fails (connection lost, SQLite lock timeout).","commonSituations":"Nodes logging out during DB maintenance windows; ephemeral nodes being deleted while their peers stream updates (lock contention on SQLite); DB connection pool exhaustion under load.","solutions":["Read the wrapped error in logs to identify the failing operation (delete vs expiry update) and the DB-level cause.","Stabilize the database: for SQLite avoid concurrent writers and check file permissions; for PostgreSQL check connection limits and restart policies.","Retry the operation from the client — logout expiry updates are idempotent, a retry after DB recovery succeeds.","If caused by ephemeral-node deletion races, upgrade headscale (state-layer locking improvements land regularly) before deep-diving."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"resp, err := h.handleRegister(req, mk)\nif err != nil && strings.Contains(err.Error(), \"handling existing node\") {\n    // logout of a known node failed at the state layer; retry after DB recovery or expire via CLI\n}","preventionTips":["Schedule node logouts away from DB maintenance windows.","On persistent failure, use `headscale nodes expire` as the operator-side equivalent.","Monitor SQLite lock timeouts under node churn."],"tags":["authentication","logout","database","registration"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}