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

  1. Compare the deadline and now= values in the message: if now itself looks wrong, fix the client clock and sync NTP, then re-login.
  2. If the client clock is right, the server published an expired deadline - capture the values and report them with the management version.
  3. Sync system time (timedatectl / w32tm / NTP) and retry; the next Sync pushes a fresh deadline.
  4. 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

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


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/16ef9b06756306d2. Report an issue: GitHub.