netbirdio/netbird · warning · ErrDeadlineBeforeEpoch
%w: %v
Error message
%w: %v
What it means
sessionwatch.Watcher.Update rejected the SSO session expiry deadline published by management because it pre-dates the Unix epoch (before 1970-01-01). The watcher first clears its own state and notifies the recorder, then returns the sentinel ErrDeadlineBeforeEpoch wrapped with the offending timestamp. It signals a malformed timestamp on the wire rather than a real session deadline, and is designed for errors.Is matching.
Source
Thrown at client/internal/auth/sessionwatch/watcher.go:160
// applySessionDeadline forces a zero deadline into the status recorder
// after a non-nil error).
func (w *Watcher) Update(deadline time.Time) error {
w.mu.Lock()
if w.closed {
w.mu.Unlock()
return nil
}
if deadline.IsZero() {
w.clearLocked()
return nil
}
now := time.Now()
switch {
case deadline.Before(time.Unix(0, 0)):
w.clearLocked()
return fmt.Errorf("%w: %v", ErrDeadlineBeforeEpoch, deadline)
case deadline.After(now.Add(maxDeadlineHorizon)):
w.clearLocked()
return fmt.Errorf("%w: %v", ErrDeadlineTooFarFuture, deadline)
case deadline.Before(now.Add(-maxPastHorizon)):
w.clearLocked()
return fmt.Errorf("%w: %v (now=%v)", ErrDeadlineInPast, deadline, now)
}
if deadline.Equal(w.current) {
w.mu.Unlock()
return nil
}
w.stopTimerLocked()
w.current = deadline
// Reset every per-deadline guard so a refreshed deadline arms a fresh
// warning cycle: both edge triggers and the user Dismiss decision
// (the user agreed to the old deadline expiring; a new deadlineView on GitHub (pinned to 93e97f4bf1)
Solutions
- Handle it as 'no deadline': the watcher already cleared its state, so push a zero deadline into any other sinks (applySessionDeadline does exactly this on error).
- Report the agent and management versions together with the rejected value printed after the colon - the timestamp is malformed on the wire and needs a server-side fix.
- Upgrade the management service if it is older than the session-expiry feature; a current server omits the field instead of sending a bogus value.
- If self-hosted, inspect the LoginResponse/SyncResponse the server actually emits for the expiry field.
Defensive patterns
Strategy: type-guard
Validate before calling
// validate a session deadline before feeding it to the watcher
func validSessionDeadline(d time.Time) bool {
if d.IsZero() {
return true // clearing is always allowed
}
return !d.Before(time.Unix(0, 0)) &&
d.Before(time.Now().Add(10*365*24*time.Hour)) &&
d.After(time.Now().Add(-30*24*time.Hour))
} Type guard
func isDeadlineSanityErr(err error) bool {
return errors.Is(err, sessionwatch.ErrDeadlineBeforeEpoch) ||
errors.Is(err, sessionwatch.ErrDeadlineTooFarFuture) ||
errors.Is(err, sessionwatch.ErrDeadlineInPast)
} Try / catch
if err := w.Update(deadline); err != nil {
if errors.Is(err, sessionwatch.ErrDeadlineBeforeEpoch) {
// protocol glitch: watcher already cleared; force zero deadline into
// other sinks and log the raw value for a server-side fix
}
} Prevention
- Validate timestamps at the boundary where SyncResponse/LoginResponse fields are read, before they reach the watcher.
- Keep agent and management versions aligned so the expiry field's semantics match.
- Unit-multiply explicitly (time.Unix(sec, 0)) rather than trusting raw int64 fields as time.Time.
- Log rejected values with both the deadline and now so skew is diagnosable from one line.
When it happens
Trigger: Update(deadline) is called with a value where deadline.Before(time.Unix(0,0)) is true: management serialized a negative expiry, sent an unset field that deserializes to a negative value, or agent/management version skew misinterprets the field (for example a signed vs unsigned or unit mismatch producing negative nanoseconds).
Common situations: Management version that predates the session-expiry feature leaving the field unset in a way the agent misreads; protobuf schema drift between agent and management; a management bug setting expiry from an uninitialized variable.
Related errors
- %w: %v (now=%v)
- management client is not initialised
- private services cannot enable bearer auth (SSO): NetBird-on
- user group name cannot be empty
- JWT already used
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/1bfcbebf66e851c9.
Report an issue: GitHub.