juanfont/headscale · error · ErrNodeNotInNodeStore
node no longer exists in NodeStore
Error message
node no longer exists in NodeStore
What it means
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.
Source
Thrown at hscontrol/state/state.go:75
// before rebuilding the in-memory node snapshot.
defaultNodeStoreBatchSize = 100
// defaultNodeStoreBatchTimeout is the default maximum time to wait before
// processing a partial batch of node operations.
defaultNodeStoreBatchTimeout = 500 * time.Millisecond
)
// ErrUnsupportedPolicyMode is returned for invalid policy modes. Valid modes are "file" and "db".
var ErrUnsupportedPolicyMode = errors.New("unsupported policy mode")
// ErrNodeNotFound is returned when a node cannot be found by its ID.
var ErrNodeNotFound = errors.New("node not found")
// ErrInvalidNodeView is returned when an invalid node view is provided.
var ErrInvalidNodeView = errors.New("invalid node view provided")
// ErrNodeNotInNodeStore is returned when a node no longer exists in the [NodeStore].
var ErrNodeNotInNodeStore = errors.New("node no longer exists in NodeStore")
// ErrNodeNameNotUnique is returned when a node name is not unique.
var ErrNodeNameNotUnique = errors.New("node name is not unique")
// nodeUpdateColumns lists all Node columns that should be written
// during a struct-based GORM Updates() call. Listing them explicitly
// forces GORM to include nil/zero-value fields (e.g. UserID=nil when
// converting a user-owned node to tagged) that struct-based Updates()
// would otherwise silently skip.
//
// Excluded columns:
// - AuthKeyID, AuthKey: prevents GORM from persisting stale
// PreAuthKey references after a key has been deleted (#2862).
// - User: GORM association, not a real column.
// - IsOnline: runtime-only field (gorm:"-").
//
// Expiry is included here but may be omitted at call sites that must
// not touch it (see persistNodeToDB).View on GitHub (pinned to 565fd254d0)
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)
Example fix
null
Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
null
Try / catch
if errors.Is(err, state.ErrNodeNotInNodeStore) {
// node vanished mid-operation: re-fetch; if gone, abandon the update
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- node name is not unique
- node not found
- route is not available on node
- failed to convert node interface
- node not found
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/a534991bdf064603.
Report an issue: GitHub.