{"record":{"id":"a534991bdf064603","repo":"juanfont/headscale","slug":"node-no-longer-exists-in-nodestore","errorCode":null,"errorMessage":"node no longer exists in NodeStore","messagePattern":"node no longer exists in NodeStore","errorType":"http","errorClass":"ErrNodeNotInNodeStore","httpStatus":404,"severity":"error","filePath":"hscontrol/state/state.go","lineNumber":75,"sourceCode":"\t// before rebuilding the in-memory node snapshot.\n\tdefaultNodeStoreBatchSize = 100\n\n\t// defaultNodeStoreBatchTimeout is the default maximum time to wait before\n\t// processing a partial batch of node operations.\n\tdefaultNodeStoreBatchTimeout = 500 * time.Millisecond\n)\n\n// ErrUnsupportedPolicyMode is returned for invalid policy modes. Valid modes are \"file\" and \"db\".\nvar ErrUnsupportedPolicyMode = errors.New(\"unsupported policy mode\")\n\n// ErrNodeNotFound is returned when a node cannot be found by its ID.\nvar ErrNodeNotFound = errors.New(\"node not found\")\n\n// ErrInvalidNodeView is returned when an invalid node view is provided.\nvar ErrInvalidNodeView = errors.New(\"invalid node view provided\")\n\n// ErrNodeNotInNodeStore is returned when a node no longer exists in the [NodeStore].\nvar ErrNodeNotInNodeStore = errors.New(\"node no longer exists in NodeStore\")\n\n// ErrNodeNameNotUnique is returned when a node name is not unique.\nvar ErrNodeNameNotUnique = errors.New(\"node name is not unique\")\n\n// nodeUpdateColumns lists all Node columns that should be written\n// during a struct-based GORM Updates() call.  Listing them explicitly\n// forces GORM to include nil/zero-value fields (e.g. UserID=nil when\n// converting a user-owned node to tagged) that struct-based Updates()\n// would otherwise silently skip.\n//\n// Excluded columns:\n//   - AuthKeyID, AuthKey: prevents GORM from persisting stale\n//     PreAuthKey references after a key has been deleted (#2862).\n//   - User: GORM association, not a real column.\n//   - IsOnline: runtime-only field (gorm:\"-\").\n//\n// Expiry is included here but may be omitted at call sites that must\n// not touch it (see persistNodeToDB).","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/state/state.go#L57-L93","documentation":"ErrNodeNotInNodeStore is a sentinel error in hscontrol/state/state.go:75 indicating that a node being updated no longer exists in the in-memory copy-on-write NodeStore snapshot. Write paths wrap it with the node ID (state.go:543, 912, 983, 1018, 3182). It differs from ErrNodeNotFound: the node vanished from the live store during an in-flight operation, typically because it was deleted concurrently.","triggerScenarios":"UpdateNodeFromMapRequest, SetTags, SetGivenName, or approve-route operations racing with a node deletion; a MapRequest arriving from a node that was just deleted; batched NodeStore writes discovering the target node's snapshot entry is gone.","commonSituations":"Admin deletes/expiry-reaps a node at the same moment the node sends a MapRequest or an API call mutates it; test suites tearing down nodes while async mappers still stream updates.","solutions":["Treat as a stale-reference condition: re-fetch the node by ID and abort the mutation if absent","If the node should still exist, check deletion/expiry logs to find who removed it","In concurrent admin tooling, serialize delete + update operations per node ID","Map to 404 in API handlers rather than 500 (v1 errors.go already maps it alongside ErrNodeNotFound)"],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"if errors.Is(err, state.ErrNodeNotInNodeStore) {\n    // node vanished mid-operation: re-fetch; if gone, abandon the update\n}","preventionTips":["Serialize delete and update operations per node ID in admin tooling","Treat this error as non-retryable until the node's existence is re-verified"],"tags":["headscale","node","race","nodestore","concurrency"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}