netbirdio/netbird · warning · ErrDeadlineInPast
%w: %v (now=%v)
Error message
%w: %v (now=%v)
What it means
sessionwatch.Watcher.Update rejected the SSO session expiry deadline because it lies more than maxPastHorizon (30 days) in the past. The message deliberately includes now= so a reader can immediately see the delta between client clock and the deadline. The watcher clears its state and returns the sentinel ErrDeadlineInPast.
Source
Thrown at client/internal/auth/sessionwatch/watcher.go:166
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 deadline
// restarts the contract).
w.firedAt = time.Time{}
w.finalFiredAt = time.Time{}
w.dismissedAt = time.Time{}
if deadline.After(now) {View on GitHub (pinned to 93e97f4bf1)
Solutions
- Compare the deadline and now= values in the message: if now itself looks wrong, fix the client clock and sync NTP, then re-login.
- If the client clock is right, the server published an expired deadline - capture the values and report them with the management version.
- Sync system time (timedatectl / w32tm / NTP) and retry; the next Sync pushes a fresh deadline.
- Treat it as 'no deadline' in code: the watcher cleared state and the recorder was reset.
Defensive patterns
Strategy: type-guard
Validate before calling
func deadlineNotAncient(d time.Time) bool {
return d.IsZero() || d.After(time.Now().Add(-30*24*time.Hour))
} Type guard
func isDeadlineInPastErr(err error) bool {
return errors.Is(err, sessionwatch.ErrDeadlineInPast)
} Try / catch
if err := w.Update(deadline); err != nil {
if errors.Is(err, sessionwatch.ErrDeadlineInPast) {
// message carries now=... : compare it with the true wall clock to
// distinguish client clock skew from a stale server deadline
}
} Prevention
- Sync system time via NTP before login on freshly booted VMs and laptops.
- Audit sleep/wake handling so deadlines are not computed from frozen clocks.
- On the server, never publish an expiry earlier than the session's issue time.
When it happens
Trigger: Update(deadline) with deadline.Before(time.Now().Add(-30*24*time.Hour)): the client clock is off by weeks; management published an already-long-expired deadline due to a bug; unit confusion turned a future timestamp into a far-past one.
Common situations: Client system clock wrong by months (dead CMOS battery, fresh VM without NTP, dual-boot clock skew); management sending an expiry computed from a stale or zero-based session; clock rollback after a scheduled-wakeup bug on laptops.
Related errors
- %w: %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/16ef9b06756306d2.
Report an issue: GitHub.