diff --git a/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift b/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift
index 1814c118..f434e31d 100644
--- a/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift	
+++ b/Coder Desktop/Coder Desktop/Coder_DesktopApp.swift	
@@ -50,11 +50,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
             object: nil
         )
         Task {
-            // If there's no NE config, then the user needs to sign in.
-            // However, they might have a session from a previous install, so we
-            // need to clear it.
+            // If there's no NE config, but the user is logged in, such as
+            // from a previous install, then we need to reconfigure.
             if await !vpn.loadNetworkExtensionConfig() {
-                state.clearSession()
+                state.reconfigure()
             }
         }
     }
diff --git a/Coder Desktop/Coder Desktop/MenuBarIconController.swift b/Coder Desktop/Coder Desktop/MenuBarIconController.swift
index 867e1837..09c73812 100644
--- a/Coder Desktop/Coder Desktop/MenuBarIconController.swift	
+++ b/Coder Desktop/Coder Desktop/MenuBarIconController.swift	
@@ -13,6 +13,8 @@ class MenuBarController {
 
     init(menuBarExtra: FluidMenuBarExtra) {
         self.menuBarExtra = menuBarExtra
+        // Off by default, as `vpnDidUpdate` isn't called until the VPN is configured
+        menuBarExtra.setOpacity(offOpacity)
     }
 
     func vpnDidUpdate(_ connection: NETunnelProviderSession) {
diff --git a/Coder Desktop/Coder Desktop/State.swift b/Coder Desktop/Coder Desktop/State.swift
index ae63f4c5..a8404ff6 100644
--- a/Coder Desktop/Coder Desktop/State.swift	
+++ b/Coder Desktop/Coder Desktop/State.swift	
@@ -32,7 +32,7 @@ class AppState: ObservableObject {
 
     @Published var useLiteralHeaders: Bool = UserDefaults.standard.bool(forKey: Keys.useLiteralHeaders) {
         didSet {
-            if let onChange { onChange(tunnelProviderProtocol()) }
+            reconfigure()
             guard persistent else { return }
             UserDefaults.standard.set(useLiteralHeaders, forKey: Keys.useLiteralHeaders)
         }
@@ -40,7 +40,7 @@ class AppState: ObservableObject {
 
     @Published var literalHeaders: [LiteralHeader] {
         didSet {
-            if let onChange { onChange(tunnelProviderProtocol()) }
+            reconfigure()
             guard persistent else { return }
             try? UserDefaults.standard.set(JSONEncoder().encode(literalHeaders), forKey: Keys.literalHeaders)
         }
@@ -70,9 +70,13 @@ class AppState: ObservableObject {
     private let keychain: Keychain
     private let persistent: Bool
 
-    // This closure must be called when any property used to configure the VPN changes
     let onChange: ((NETunnelProviderProtocol?) -> Void)?
 
+    // reconfigure must be called when any property used to configure the VPN changes
+    public func reconfigure() {
+        if let onChange { onChange(tunnelProviderProtocol()) }
+    }
+
     public init(onChange: ((NETunnelProviderProtocol?) -> Void)? = nil,
                 persistent: Bool = true)
     {
@@ -97,13 +101,13 @@ class AppState: ObservableObject {
         hasSession = true
         self.baseAccessURL = baseAccessURL
         self.sessionToken = sessionToken
-        if let onChange { onChange(tunnelProviderProtocol()) }
+        reconfigure()
     }
 
     public func clearSession() {
         hasSession = false
         sessionToken = nil
-        if let onChange { onChange(tunnelProviderProtocol()) }
+        reconfigure()
     }
 
     private func keychainGet(for key: String) -> String? {
diff --git a/Coder Desktop/Coder Desktop/Views/LoginForm.swift b/Coder Desktop/Coder Desktop/Views/LoginForm.swift
index acebb070..881c1a87 100644
--- a/Coder Desktop/Coder Desktop/Views/LoginForm.swift	
+++ b/Coder Desktop/Coder Desktop/Views/LoginForm.swift	
@@ -38,7 +38,7 @@ struct LoginForm: View {
         .animation(.easeInOut, value: currentPage)
         .onAppear {
             baseAccessURL = state.baseAccessURL?.absoluteString ?? baseAccessURL
-            sessionToken = ""
+            sessionToken = state.sessionToken ?? sessionToken
         }
         .alert("Error", isPresented: Binding(
             get: { loginError != nil },
@@ -122,7 +122,7 @@ struct LoginForm: View {
                     ).disabled(true)
                 }
                 Section {
-                    SecureField("Session Token", text: $sessionToken, prompt: Text("●●●●●●●●"))
+                    SecureField("Session Token", text: $sessionToken)
                         .autocorrectionDisabled()
                         .privacySensitive()
                         .focused($focusedField, equals: .sessionToken)
diff --git a/Coder Desktop/VPN/Manager.swift b/Coder Desktop/VPN/Manager.swift
index f1e5cdfb..f074abb8 100644
--- a/Coder Desktop/VPN/Manager.swift	
+++ b/Coder Desktop/VPN/Manager.swift	
@@ -30,6 +30,10 @@ actor Manager {
             let sessionConfig = URLSessionConfiguration.default
             // The tunnel might be asked to start before the network interfaces have woken up from sleep
             sessionConfig.waitsForConnectivity = true
+            // URLSession's waiting for connectivity sometimes hangs even when
+            // the network is up so this is deliberately short (15s) to avoid a
+            // poor UX where it appears stuck.
+            sessionConfig.timeoutIntervalForResource = 15
             try await download(src: dylibPath, dest: dest, urlSession: URLSession(configuration: sessionConfig))
         } catch {
             throw .download(error)