diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ee602d8d..a2239cf9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,9 +34,7 @@ jobs:
       - name: Switch XCode Version
         uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
         with:
-          # (ThomasK33): depot.dev does not yet support Xcode 16.1 or 16.2 GA, thus we're stuck with 16.0.0 for now.
-          # I've already reached out, so hopefully this comment will soon be obsolete.
-          xcode-version: "16.0.0"
+          xcode-version: "16.4.0"
 
       - name: Setup Nix
         uses: ./.github/actions/nix-devshell
@@ -57,9 +55,7 @@ jobs:
       - name: Switch XCode Version
         uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
         with:
-          # (ThomasK33): depot.dev does not yet support Xcode 16.1 or 16.2 GA, thus we're stuck with 16.0.0 for now.
-          # I've already reached out, so hopefully this comment will soon be obsolete.
-          xcode-version: "16.0.0"
+          xcode-version: "16.4.0"
 
       - name: Setup Nix
         uses: ./.github/actions/nix-devshell
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index cd62aa6e..96c7c4d9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -43,7 +43,7 @@ jobs:
       - name: Switch XCode Version
         uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
         with:
-          xcode-version: "16.0.0"
+          xcode-version: "16.4.0"
 
       - name: Setup Nix
         uses: ./.github/actions/nix-devshell
diff --git a/Coder-Desktop/.swiftformat b/Coder-Desktop/.swiftformat
index b34aa3f1..388c4a61 100644
--- a/Coder-Desktop/.swiftformat
+++ b/Coder-Desktop/.swiftformat
@@ -1,3 +1,4 @@
 --selfrequired log,info,error,debug,critical,fault
 --exclude **.pb.swift,**.grpc.swift
---condassignment always
\ No newline at end of file
+--condassignment always
+--disable unusedArguments
\ No newline at end of file
diff --git a/Coder-Desktop/.swiftlint.yml b/Coder-Desktop/.swiftlint.yml
index 1c2e5c48..1cf2d055 100644
--- a/Coder-Desktop/.swiftlint.yml
+++ b/Coder-Desktop/.swiftlint.yml
@@ -3,6 +3,8 @@ disabled_rules:
   - trailing_comma
   - blanket_disable_command # Used by Protobuf
   - opening_brace # Handled by SwiftFormat
+opt_in_rules:
+  - unused_parameter
 type_name:
   allowed_symbols: "_"
 identifier_name:
diff --git a/Coder-Desktop/Coder-Desktop/UpdaterService.swift b/Coder-Desktop/Coder-Desktop/UpdaterService.swift
index 23b86b84..ce7bc9d2 100644
--- a/Coder-Desktop/Coder-Desktop/UpdaterService.swift
+++ b/Coder-Desktop/Coder-Desktop/UpdaterService.swift
@@ -63,6 +63,10 @@ extension UpdaterService: SPUUpdaterDelegate {
         // preview >= stable
         [updateChannel.rawValue]
     }
+
+    func updater(_: SPUUpdater, didFindValidUpdate _: SUAppcastItem) {
+        Task { @MainActor in appActivate() }
+    }
 }
 
 extension UpdaterService: SUVersionDisplay {
diff --git a/Coder-Desktop/Coder-Desktop/VPN/NetworkExtension.swift b/Coder-Desktop/Coder-Desktop/VPN/NetworkExtension.swift
index 7c90bd5d..3f325cdb 100644
--- a/Coder-Desktop/Coder-Desktop/VPN/NetworkExtension.swift
+++ b/Coder-Desktop/Coder-Desktop/VPN/NetworkExtension.swift
@@ -16,7 +16,7 @@ enum NetworkExtensionState: Equatable {
         case .disabled:
             "NetworkExtension tunnel disabled"
         case let .failed(error):
-            "NetworkExtension config failed: \(error)"
+            "NetworkExtension: \(error)"
         }
     }
 }
@@ -44,7 +44,7 @@ extension CoderVPNService {
             try await removeNetworkExtension()
         } catch {
             logger.error("remove tunnel failed: \(error)")
-            neState = .failed(error.localizedDescription)
+            neState = .failed("Failed to remove configuration: \(error.description)")
             return
         }
         logger.debug("inserting new tunnel")
@@ -60,7 +60,9 @@ extension CoderVPNService {
         } catch {
             // This typically fails when the user declines the permission dialog
             logger.error("save tunnel failed: \(error)")
-            neState = .failed("Failed to save tunnel: \(error.localizedDescription). Try logging in and out again.")
+            neState = .failed(
+                "Failed to save configuration: \(error.localizedDescription). Try logging in and out again."
+            )
         }
     }
 
@@ -71,17 +73,24 @@ extension CoderVPNService {
                 try await tunnel.removeFromPreferences()
             }
         } catch {
-            throw .internalError("couldn't remove tunnels: \(error)")
+            throw .internalError(error.localizedDescription)
         }
     }
 
     func startTunnel() async {
+        let tm: NETunnelProviderManager
+        do {
+            tm = try await getTunnelManager()
+        } catch {
+            logger.error("get tunnel: \(error)")
+            neState = .failed("Failed to get VPN configuration: \(error.description)")
+            return
+        }
         do {
-            let tm = try await getTunnelManager()
             try tm.connection.startVPNTunnel()
         } catch {
             logger.error("start tunnel: \(error)")
-            neState = .failed(error.localizedDescription)
+            neState = .failed("Failed to start VPN tunnel: \(error.localizedDescription)")
             return
         }
         logger.debug("started tunnel")
@@ -94,7 +103,7 @@ extension CoderVPNService {
             tm.connection.stopVPNTunnel()
         } catch {
             logger.error("stop tunnel: \(error)")
-            neState = .failed(error.localizedDescription)
+            neState = .failed("Failed to stop VPN tunnel: \(error.localizedDescription)")
             return
         }
         logger.debug("stopped tunnel")
diff --git a/Coder-Desktop/Coder-Desktop/VPN/VPNService.swift b/Coder-Desktop/Coder-Desktop/VPN/VPNService.swift
index 224174ae..1bf4a842 100644
--- a/Coder-Desktop/Coder-Desktop/VPN/VPNService.swift
+++ b/Coder-Desktop/Coder-Desktop/VPN/VPNService.swift
@@ -37,7 +37,7 @@ enum VPNServiceError: Error, Equatable {
     case systemExtensionError(SystemExtensionState)
     case networkExtensionError(NetworkExtensionState)
 
-    var description: String {
+    public var description: String {
         switch self {
         case let .internalError(description):
             "Internal Error: \(description)"
@@ -48,7 +48,7 @@ enum VPNServiceError: Error, Equatable {
         }
     }
 
-    var localizedDescription: String { description }
+    public var localizedDescription: String { description }
 }
 
 @MainActor
@@ -126,13 +126,13 @@ final class CoderVPNService: NSObject, VPNService {
                 // this just configures the VPN, it doesn't enable it
                 tunnelState = .disabled
             } else {
-                do {
+                do throws(VPNServiceError) {
                     try await removeNetworkExtension()
                     neState = .unconfigured
                     tunnelState = .disabled
                 } catch {
-                    logger.error("failed to remove network extension: \(error)")
-                    neState = .failed(error.localizedDescription)
+                    logger.error("failed to remove configuration: \(error)")
+                    neState = .failed("Failed to remove configuration: \(error.description)")
                 }
             }
         }
diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift
index 302bd135..c0750567 100644
--- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift
@@ -165,11 +165,11 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
     }
 
     // TODO: Support selecting & deleting multiple sessions at once
-    func delete(session _: FileSyncSession) async {
+    func delete(session: FileSyncSession) async {
         loading = true
         defer { loading = false }
         do throws(DaemonError) {
-            try await fileSync.deleteSessions(ids: [selection!])
+            try await fileSync.deleteSessions(ids: [session.id])
         } catch {
             actionError = error
         }
diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
index b5108670..63a1faaa 100644
--- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
@@ -28,6 +28,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
                         Button {
                             let panel = NSOpenPanel()
                             panel.directoryURL = FileManager.default.homeDirectoryForCurrentUser
+                            panel.canCreateDirectories = true
                             panel.allowsMultipleSelection = false
                             panel.canChooseDirectories = true
                             panel.canChooseFiles = false
diff --git a/Coder-Desktop/Coder-Desktop/Views/LoginForm.swift b/Coder-Desktop/Coder-Desktop/Views/LoginForm.swift
index d2880dda..5e9227ff 100644
--- a/Coder-Desktop/Coder-Desktop/Views/LoginForm.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/LoginForm.swift
@@ -58,6 +58,7 @@ struct LoginForm: View {
 
     func submit() async {
         loginError = nil
+        sessionToken = sessionToken.trimmingCharacters(in: .whitespacesAndNewlines)
         guard sessionToken != "" else {
             return
         }
@@ -164,6 +165,7 @@ struct LoginForm: View {
     }
 
     private func next() {
+        baseAccessURL = baseAccessURL.trimmingCharacters(in: .whitespacesAndNewlines)
         guard baseAccessURL != "" else {
             return
         }
diff --git a/Coder-Desktop/Coder-Desktop/Views/VPN/Agents.swift b/Coder-Desktop/Coder-Desktop/Views/VPN/Agents.swift
index 33fa71c5..58df8d31 100644
--- a/Coder-Desktop/Coder-Desktop/Views/VPN/Agents.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/VPN/Agents.swift
@@ -16,28 +16,32 @@ struct Agents<VPN: VPNService>: View {
             if vpn.state == .connected {
                 let items = vpn.menuState.sorted
                 let visibleItems = viewAll ? items[...] : items.prefix(defaultVisibleRows)
-                ForEach(visibleItems, id: \.id) { agent in
-                    MenuItemView(
-                        item: agent,
-                        baseAccessURL: state.baseAccessURL!,
-                        expandedItem: $expandedItem,
-                        userInteracted: $hasToggledExpansion
-                    )
-                    .padding(.horizontal, Theme.Size.trayMargin)
-                }.onChange(of: visibleItems) {
-                    // If no workspaces are online, we should expand the first one to come online
-                    if visibleItems.filter({ $0.status != .off }).isEmpty {
-                        hasToggledExpansion = false
-                        return
+                ScrollView(showsIndicators: false) {
+                    ForEach(visibleItems, id: \.id) { agent in
+                        MenuItemView(
+                            item: agent,
+                            baseAccessURL: state.baseAccessURL!,
+                            expandedItem: $expandedItem,
+                            userInteracted: $hasToggledExpansion
+                        )
+                        .padding(.horizontal, Theme.Size.trayMargin)
+                    }.onChange(of: visibleItems) {
+                        // If no workspaces are online, we should expand the first one to come online
+                        if visibleItems.filter({ $0.status != .off }).isEmpty {
+                            hasToggledExpansion = false
+                            return
+                        }
+                        if hasToggledExpansion {
+                            return
+                        }
+                        withAnimation(.snappy(duration: Theme.Animation.collapsibleDuration)) {
+                            expandedItem = visibleItems.first?.id
+                        }
+                        hasToggledExpansion = true
                     }
-                    if hasToggledExpansion {
-                        return
-                    }
-                    withAnimation(.snappy(duration: Theme.Animation.collapsibleDuration)) {
-                        expandedItem = visibleItems.first?.id
-                    }
-                    hasToggledExpansion = true
                 }
+                .scrollBounceBehavior(.basedOnSize)
+                .frame(maxHeight: 400)
                 if items.count == 0 {
                     Text("No workspaces!")
                         .font(.body)
diff --git a/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift b/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift
index 2a9e2254..a48be35f 100644
--- a/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift
@@ -117,12 +117,15 @@ struct VPNMenu<VPN: VPNService, FS: FileSyncDaemon>: View {
     }
 
     private var vpnDisabled: Bool {
-        vpn.state == .connecting ||
-            vpn.state == .disconnecting ||
-            // Prevent starting the VPN before the user has approved the system extension.
-            vpn.state == .failed(.systemExtensionError(.needsUserApproval)) ||
-            // Prevent starting the VPN without a VPN configuration.
-            vpn.state == .failed(.networkExtensionError(.unconfigured))
+        // Always enabled if signed out, as that will open the sign in window
+        state.hasSession && (
+            vpn.state == .connecting ||
+                vpn.state == .disconnecting ||
+                // Prevent starting the VPN before the user has approved the system extension.
+                vpn.state == .failed(.systemExtensionError(.needsUserApproval)) ||
+                // Prevent starting the VPN without a VPN configuration.
+                vpn.state == .failed(.networkExtensionError(.unconfigured))
+        )
     }
 }
 
diff --git a/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift b/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift
index 880241a0..3446429e 100644
--- a/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift
@@ -138,24 +138,25 @@ struct MenuItemView: View {
                 MenuItemIcons(item: item, wsURL: wsURL)
             }
             if isExpanded {
-                switch (loadingApps, hasApps) {
-                case (true, _):
-                    CircularProgressView(value: nil, strokeWidth: 3, diameter: 15)
-                        .padding(.top, 5)
-                case (false, true):
-                    MenuItemCollapsibleView(apps: apps)
-                case (false, false):
-                    HStack {
-                        Text(item.status == .off ? "Workspace is offline." : "No apps available.")
-                            .font(.body)
-                            .foregroundColor(.secondary)
-                            .padding(.horizontal, Theme.Size.trayInset)
-                            .padding(.top, 7)
+                Group {
+                    switch (loadingApps, hasApps) {
+                    case (true, _):
+                        CircularProgressView(value: nil, strokeWidth: 3, diameter: 15)
+                            .padding(.top, 5)
+                    case (false, true):
+                        MenuItemCollapsibleView(apps: apps)
+                    case (false, false):
+                        HStack {
+                            Text(item.status == .off ? "Workspace is offline." : "No apps available.")
+                                .font(.body)
+                                .foregroundColor(.secondary)
+                                .padding(.horizontal, Theme.Size.trayInset)
+                                .padding(.top, 7)
+                        }
                     }
-                }
+                }.task { await loadApps() }
             }
         }
-        .task { await loadApps() }
     }
 
     func loadApps() async {
diff --git a/Coder-Desktop/Coder-DesktopTests/VPNMenuTests.swift b/Coder-Desktop/Coder-DesktopTests/VPNMenuTests.swift
index 46c780ca..322952bf 100644
--- a/Coder-Desktop/Coder-DesktopTests/VPNMenuTests.swift
+++ b/Coder-Desktop/Coder-DesktopTests/VPNMenuTests.swift
@@ -32,6 +32,21 @@ struct VPNMenuTests {
         }
     }
 
+    @Test
+    func testVPNLoggedOutUnconfigured() async throws {
+        vpn.state = .failed(.networkExtensionError(.unconfigured))
+        try await ViewHosting.host(view) {
+            try await sut.inspection.inspect { view in
+                let toggle = try view.find(ViewType.Toggle.self)
+                // Toggle should be enabled even with a failure that would
+                // normally make it disabled, because we're signed out.
+                #expect(!toggle.isDisabled())
+                #expect(throws: Never.self) { try view.find(text: "Sign in to use Coder Desktop") }
+                #expect(throws: Never.self) { try view.find(button: "Sign in") }
+            }
+        }
+    }
+
     @Test
     func testStartStopCalled() async throws {
         try await ViewHosting.host(view) {
@@ -59,6 +74,7 @@ struct VPNMenuTests {
     @Test
     func testVPNDisabledWhileConnecting() async throws {
         vpn.state = .disabled
+        state.login(baseAccessURL: URL(string: "https://coder.example.com")!, sessionToken: "fake-token")
 
         try await ViewHosting.host(view) {
             try await sut.inspection.inspect { view in
@@ -79,6 +95,7 @@ struct VPNMenuTests {
     @Test
     func testVPNDisabledWhileDisconnecting() async throws {
         vpn.state = .disabled
+        state.login(baseAccessURL: URL(string: "https://coder.example.com")!, sessionToken: "fake-token")
 
         try await ViewHosting.host(view) {
             try await sut.inspection.inspect { view in
diff --git a/Coder-Desktop/project.yml b/Coder-Desktop/project.yml
index 166a1570..f97ebddd 100644
--- a/Coder-Desktop/project.yml
+++ b/Coder-Desktop/project.yml
@@ -98,7 +98,7 @@ packages:
     # - Set onAppear/disappear handlers.
     # The upstream repo has a purposefully limited API
     url: https://github.com/coder/fluid-menu-bar-extra
-    revision: 8e1d8b8
+    revision: b0d5438
   KeychainAccess:
     url: https://github.com/kishikawakatsumi/KeychainAccess
     branch: e0c7eebc5a4465a3c4680764f26b7a61f567cdaf
