diff --git a/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift b/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift
index 1d379e9..97e5e70 100644
--- a/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift
+++ b/Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift
@@ -40,11 +40,21 @@ class AppDelegate: NSObject, NSApplicationDelegate {
     }
 
     func applicationDidFinishLaunching(_: Notification) {
-        menuBar = .init(menuBarExtra: FluidMenuBarExtra(title: "Coder Desktop", image: "MenuBarIcon") {
-            VPNMenu<CoderVPNService>().frame(width: 256)
-                .environmentObject(self.vpn)
-                .environmentObject(self.state)
-        })
+        menuBar = .init(menuBarExtra: FluidMenuBarExtra(
+            title: "Coder Desktop",
+            image: "MenuBarIcon",
+            onAppear: {
+                // If the VPN is enabled, it's likely the token isn't expired
+                guard case .disabled = self.vpn.state, self.state.hasSession else { return }
+                Task { @MainActor in
+                    await self.state.handleTokenExpiry()
+                }
+            }, content: {
+                VPNMenu<CoderVPNService>().frame(width: 256)
+                    .environmentObject(self.vpn)
+                    .environmentObject(self.state)
+            }
+        ))
         // Subscribe to system VPN updates
         NotificationCenter.default.addObserver(
             self,
diff --git a/Coder-Desktop/Coder-Desktop/State.swift b/Coder-Desktop/Coder-Desktop/State.swift
index 3e723c9..b394468 100644
--- a/Coder-Desktop/Coder-Desktop/State.swift
+++ b/Coder-Desktop/Coder-Desktop/State.swift
@@ -2,10 +2,12 @@ import CoderSDK
 import Foundation
 import KeychainAccess
 import NetworkExtension
+import os
 import SwiftUI
 
 @MainActor
 class AppState: ObservableObject {
+    private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "AppState")
     let appId = Bundle.main.bundleIdentifier!
 
     // Stored in UserDefaults
@@ -95,6 +97,9 @@ class AppState: ObservableObject {
         )
         if hasSession {
             _sessionToken = Published(initialValue: keychainGet(for: Keys.sessionToken))
+            if sessionToken == nil || sessionToken!.isEmpty == true {
+                clearSession()
+            }
         }
     }
 
@@ -105,6 +110,24 @@ class AppState: ObservableObject {
         reconfigure()
     }
 
+    public func handleTokenExpiry() async {
+        if hasSession {
+            let client = Client(url: baseAccessURL!, token: sessionToken!)
+            do {
+                _ = try await client.user("me")
+            } catch let ClientError.api(apiErr) {
+                // Expired token
+                if apiErr.statusCode == 401 {
+                    clearSession()
+                }
+            } catch {
+                // Some other failure, we'll show an error if they try and do something
+                logger.error("failed to check token validity: \(error)")
+                return
+            }
+        }
+    }
+
     public func clearSession() {
         hasSession = false
         sessionToken = nil
diff --git a/Coder-Desktop/CoderSDK/Client.swift b/Coder-Desktop/CoderSDK/Client.swift
index 85bc8f3..239db14 100644
--- a/Coder-Desktop/CoderSDK/Client.swift
+++ b/Coder-Desktop/CoderSDK/Client.swift
@@ -104,10 +104,10 @@ public struct Client {
 }
 
 public struct APIError: Decodable, Sendable {
-    let response: Response
-    let statusCode: Int
-    let method: String
-    let url: URL
+    public let response: Response
+    public let statusCode: Int
+    public let method: String
+    public let url: URL
 
     var description: String {
         var components = ["\(method) \(url.absoluteString)\nUnexpected status code \(statusCode):\n\(response.message)"]
diff --git a/Coder-Desktop/project.yml b/Coder-Desktop/project.yml
index 5411b5a..c3c53f9 100644
--- a/Coder-Desktop/project.yml
+++ b/Coder-Desktop/project.yml
@@ -92,10 +92,12 @@ packages:
     url: https://github.com/SimplyDanny/SwiftLintPlugins
     from: 0.57.1
   FluidMenuBarExtra:
-    # Forked so we can dynamically update the menu bar icon.
+    # Forked to:
+    # - Dynamically update the menu bar icon
+    # - Set onAppear/disappear handlers.
     # The upstream repo has a purposefully limited API
     url: https://github.com/coder/fluid-menu-bar-extra
-    revision: 020be37
+    revision: 96a861a
   KeychainAccess:
     url: https://github.com/kishikawakatsumi/KeychainAccess
     branch: e0c7eebc5a4465a3c4680764f26b7a61f567cdaf