The notice appeared twenty minutes after connecting. The counter underneath it had been running since the previous morning, or since the account last authenticated, and nothing in the alert said which. That is the symptom: a reminder tied to something other than the connection in front of you.

The fix is not a better alert. It is deciding which clock the requirement means, then binding the timer to an object that exists in that clock.

What Fired, and What It Was Counting

Three plausible sources produce the same message. A calendar rule runs at a fixed local time and knows nothing about tunnels. A counter in the client accumulates while any connection is up and survives reconnects. A gateway policy expires the session server-side and pushes a warning first.

Only the last two are about a session, and even those disagree. The client counter measures what the local process observed; the gateway measures what it authenticated and accounted. A sleep, a network change or an observer restart can separate the two by hours, which is why the alert and the enforced expiry drift apart.

Three Clocks Behind One Requirement

Start by writing down the sentence you actually want. Warning before the gateway ends this connection is not the same requirement as reminding after a day of connected time. Nor is it the same as reporting that this session has been up for 23 hours. Each version names a different event, and a different component has to answer it.

Then check whether the data exists. A local start timestamp gives you session age with no dependency on the server. A gateway policy gives you remaining lifetime, but only if the gateway exposes it. A usage report gives you duration after the fact, which cannot drive an advance notice.

Bind the Timer to the Session, Not the Account

Pick the object first. An account can hold several devices and several simultaneous sessions, so a reminder keyed on the username cannot tell which connection reached the age. Key it on the profile plus that activation, and cancel the pending notice when the activation ends.

Evidence you can readWhat it establishesWhere it misleads
Connection state changes to activeA session started nowDoes not carry the gateway's own session identifier
Duration reported at disconnectTotal service time for that sessionArrives too late for any advance notice
Interim usage updateTime accounted up to that updateStaleness equals the update interval
Local timestamp taken at activationAge of your observationResets whenever the observer restarts

That table is a contract for your own notifier, not a description of any client's callback API. Whatever the installed product documents for connection state is the source to bind to; where it exposes only authentication events, say so out loud and accept the coarser behaviour instead of inferring a session.

Test It With a Short Threshold

Never debug a day-long timer with a day-long wait. Add a configurable threshold, set it to thirty seconds, and run four transitions in order: connect, wait, disconnect, reconnect. Each transition should produce exactly one state change, and the reconnect should schedule a new deadline rather than inherit the old one.

# current connection state and profile identity
nmcli -f GENERAL.STATE,GENERAL.CONNECTION connection show work

# the notifier unit you wrote: does it restart the deadline or resume it?
journalctl --user -u vpn-timer.service --since "-5 min" -o cat

Watch the timestamps rather than the wording. A notice that arrives carrying the previous session's start time bound the wrong object, and a notice that arrives twice for one deadline means the sent flag was never persisted.

Sleep, Reconnect and Missed Delivery

Suspend breaks both naive designs. A monotonic countdown stops while the machine sleeps, a wall-clock deadline keeps moving, and a timer that fires during suspend is delivered late or not at all. Decide which behaviour your requirement needs, then suspend across the threshold and confirm it.

Delivery deserves the same care. If the notice cannot be shown, queue it, and re-check that the target session still exists before finally showing it; otherwise the user is warned about a connection that already ended. Forced disconnection and credential renewal stay out of this path, because a reminder that quietly changes the connection's fate is a policy decision wearing a notification's clothes.

អត្ថបទពាក់ព័ន្ធ