juanfont/headscale · error
deleting ephemeral node: %w
Error message
deleting ephemeral node: %w
What it means
Wraps a failure of h.state.DeleteNode while removing an ephemeral node during logout (hscontrol/auth.go:220). Ephemeral nodes (registered with an ephemeral pre-auth key) are deleted from the state layer and database on logout; any DB or consistency error — foreign key constraints from related records, lock timeouts, connection failures — is surfaced with this message and the logout response is not returned.
Source
Thrown at hscontrol/auth.go:220
// If the request expiry is in the past, we consider it a logout.
// Zero expiry is handled in [Headscale.handleRegister] before calling this function.
if req.Expiry.Before(time.Now()) {
log.Debug().
EmbedObject(node).
Bool("is_ephemeral", node.IsEphemeral()).
Bool("has_authkey", node.AuthKey().Valid()).
Time("req.expiry", req.Expiry).
Msg("Processing logout request with past expiry")
if node.IsEphemeral() {
log.Info().
EmbedObject(node).
Msg("Deleting ephemeral node during logout")
c, err := h.state.DeleteNode(node)
if err != nil {
return nil, fmt.Errorf("deleting ephemeral node: %w", err)
}
h.Change(c)
return &tailcfg.RegisterResponse{
NodeKeyExpired: true,
MachineAuthorized: false,
}, nil
}
log.Debug().
EmbedObject(node).
Msg("Node is not ephemeral, setting expiry instead of deleting")
}
// Tagged nodes have key expiry permanently disabled (they are owned by
// their tags, not a user, and never expire - KB 1068). Logging one out has
// no expiry semantics, so do not stamp an expiry on it: doing so leaves theView on GitHub (pinned to 565fd254d0)
Solutions
- Check the wrapped DB error in logs; constraint violations indicate related rows not cascading — upgrade headscale (deletion order fixes land periodically).
- For lock contention, reduce simultaneous logouts (stagger termination) or switch SQLite deployments to PostgreSQL.
- Retry: the node can re-attempt logout; if the node is gone anyway, an admin can remove it manually via `headscale nodes delete -i <id>`.
- If persistent, dump logs plus schema version and report upstream.
Defensive patterns
Strategy: retry
Try / catch
resp, err := h.handleRegister(req, mk)
if err != nil && strings.Contains(err.Error(), "deleting ephemeral node") {
// teardown failed; node stays registered — retry logout or delete via `headscale nodes delete`
} Prevention
- Stagger bulk termination of ephemeral nodes (CI shutdown hooks).
- Prefer PostgreSQL for high ephemeral churn.
- Periodically reconcile with `headscale nodes list` to catch half-deleted ephemeral nodes.
When it happens
Trigger: An ephemeral node (ephemeral=true authkey) logs out while the DB is under write load; deletion trips FK constraints on related rows (routes, pre-auth-key usages); SQLite 'database is locked' during concurrent mapper updates; PostgreSQL unreachable mid-transaction.
Common situations: CI/ephemeral CI runners deregistering en masse at job end; autoscaling groups terminating ephemeral nodes simultaneously; DB maintenance overlapping with node churn.
Related errors
- handling logout: %w
- handling existing node: %w
- setting node expiry: %w
- handling register with auth key: %w
- handling register interactive: %w
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/52a304ccbee406db.
Report an issue: GitHub.