From c0b00eed46cf1b57503635b7daa139bdd072edaa Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 7 Jun 2026 19:55:56 +0000 Subject: [PATCH] [client] Surface session deadline rejections via SystemEvent and add timer arm debug logs When sessionwatch.Watcher.Update rejects a deadline (pre-epoch, too far in the future, or past the clock-skew tolerance) it silently zeroes the status recorder, leaving the UI with no "expires in" row and no indication of why. Publish a SystemEvent_ERROR on the AUTHENTICATION channel so the rejection appears in the UI event feed and the user knows re-login may be required. Also add Debugf log lines in armTimerLocked so that warning and final-warning timer fire-times are visible in logs without having to add instrumentation after the fact. https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u --- client/internal/auth/sessionwatch/watcher.go | 8 ++++++-- client/internal/engine_authsession.go | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/client/internal/auth/sessionwatch/watcher.go b/client/internal/auth/sessionwatch/watcher.go index e75a7022e..a5821c712 100644 --- a/client/internal/auth/sessionwatch/watcher.go +++ b/client/internal/auth/sessionwatch/watcher.go @@ -287,12 +287,16 @@ func (w *Watcher) stopTimerLocked() { } func (w *Watcher) armTimerLocked(deadline time.Time) { - w.timer = armOneShotLocked(deadline.Add(-w.lead), func() { w.fire(deadline) }) + fireAt := deadline.Add(-w.lead) + log.Debugf("sessionwatch: arming warning timer at %s (in %s)", fireAt.Format(time.RFC3339), time.Until(fireAt).Round(time.Second)) + w.timer = armOneShotLocked(fireAt, func() { w.fire(deadline) }) // finalLead <= 0 disables the final-warning timer entirely. Used by // tests that predate the final-warning fallback so a millisecond-scale // deadline does not flush both timers at once. if w.finalLead > 0 { - w.finalTimer = armOneShotLocked(deadline.Add(-w.finalLead), func() { w.fireFinal(deadline) }) + finalFireAt := deadline.Add(-w.finalLead) + log.Debugf("sessionwatch: arming final-warning timer at %s (in %s)", finalFireAt.Format(time.RFC3339), time.Until(finalFireAt).Round(time.Second)) + w.finalTimer = armOneShotLocked(finalFireAt, func() { w.fireFinal(deadline) }) } } diff --git a/client/internal/engine_authsession.go b/client/internal/engine_authsession.go index 49e276b77..c12b6de6b 100644 --- a/client/internal/engine_authsession.go +++ b/client/internal/engine_authsession.go @@ -9,6 +9,7 @@ import ( log "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/timestamppb" + cProto "github.com/netbirdio/netbird/client/proto" "github.com/netbirdio/netbird/client/system" ) @@ -51,6 +52,13 @@ func (e *Engine) ApplySessionDeadline(ts *timestamppb.Timestamp) { // of sync with the warning timers. if err := e.sessionWatcher.Update(deadline); err != nil { log.Errorf("auth session deadline rejected: %v, clearing", err) + e.statusRecorder.PublishEvent( + cProto.SystemEvent_ERROR, + cProto.SystemEvent_AUTHENTICATION, + "session deadline rejected", + "The session expiry time from the server could not be applied. Re-login may be required.", + map[string]string{"reason": err.Error()}, + ) } }