{"record":{"id":"16ef9b06756306d2","repo":"netbirdio/netbird","slug":"w-v-now-v","errorCode":null,"errorMessage":"%w: %v (now=%v)","messagePattern":"%w: (.+?) \\(now=(.+?)\\)","errorType":"exception","errorClass":"ErrDeadlineInPast","httpStatus":null,"severity":"warning","filePath":"client/internal/auth/sessionwatch/watcher.go","lineNumber":166,"sourceCode":"\t\treturn nil\n\t}\n\n\tif deadline.IsZero() {\n\t\tw.clearLocked()\n\t\treturn nil\n\t}\n\n\tnow := time.Now()\n\tswitch {\n\tcase deadline.Before(time.Unix(0, 0)):\n\t\tw.clearLocked()\n\t\treturn fmt.Errorf(\"%w: %v\", ErrDeadlineBeforeEpoch, deadline)\n\tcase deadline.After(now.Add(maxDeadlineHorizon)):\n\t\tw.clearLocked()\n\t\treturn fmt.Errorf(\"%w: %v\", ErrDeadlineTooFarFuture, deadline)\n\tcase deadline.Before(now.Add(-maxPastHorizon)):\n\t\tw.clearLocked()\n\t\treturn fmt.Errorf(\"%w: %v (now=%v)\", ErrDeadlineInPast, deadline, now)\n\t}\n\n\tif deadline.Equal(w.current) {\n\t\tw.mu.Unlock()\n\t\treturn nil\n\t}\n\n\tw.stopTimerLocked()\n\tw.current = deadline\n\t// Reset every per-deadline guard so a refreshed deadline arms a fresh\n\t// warning cycle: both edge triggers and the user Dismiss decision\n\t// (the user agreed to the old deadline expiring; a new deadline\n\t// restarts the contract).\n\tw.firedAt = time.Time{}\n\tw.finalFiredAt = time.Time{}\n\tw.dismissedAt = time.Time{}\n\n\tif deadline.After(now) {","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/internal/auth/sessionwatch/watcher.go#L148-L184","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"func deadlineNotAncient(d time.Time) bool {\n    return d.IsZero() || d.After(time.Now().Add(-30*24*time.Hour))\n}","typeGuard":"func isDeadlineInPastErr(err error) bool {\n    return errors.Is(err, sessionwatch.ErrDeadlineInPast)\n}","tryCatchPattern":"if err := w.Update(deadline); err != nil {\n    if errors.Is(err, sessionwatch.ErrDeadlineInPast) {\n        // message carries now=... : compare it with the true wall clock to\n        // distinguish client clock skew from a stale server deadline\n    }\n}","preventionTips":["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."],"tags":["session","sso","time","clock-skew","errors-is"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}