From 4e46f08feda7300310e320a6446ab7c97edd63cd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 13 Jul 2026 00:41:59 -0700 Subject: [PATCH] fix(macos): keep CLI path discovery off main actor (#106089) --- .../macos/Sources/OpenClaw/CLIInstaller.swift | 30 ++++++++++++++----- .../Sources/OpenClaw/CommandResolver.swift | 7 +++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/apps/macos/Sources/OpenClaw/CLIInstaller.swift b/apps/macos/Sources/OpenClaw/CLIInstaller.swift index ffdf03b6cc04..cf608e670974 100644 --- a/apps/macos/Sources/OpenClaw/CLIInstaller.swift +++ b/apps/macos/Sources/OpenClaw/CLIInstaller.swift @@ -160,8 +160,9 @@ enum CLIInstaller { } static func status() async -> Status { + let preferredPaths = await CommandResolver.preferredPathsAsync() let locations = self.installedLocations( - searchPaths: CommandResolver.preferredPaths(), + searchPaths: preferredPaths, fileManager: .default) guard !locations.isEmpty else { return .missing(location: self.managedExecutableLocation()) @@ -169,7 +170,10 @@ enum CLIInstaller { var fallbackStatus: Status? for location in locations { - let status = await self.status(location: location) + let status = await self.status( + location: location, + expectedVersion: GatewayEnvironment.expectedGatewayVersionString(), + preferredPaths: preferredPaths) if status.isReady { self.rememberValidated(status) return status @@ -189,7 +193,11 @@ enum CLIInstaller { return .missing(location: location) } - let status = await self.status(location: location, expectedVersion: expectedVersion) + let preferredPaths = await CommandResolver.preferredPathsAsync() + let status = await self.status( + location: location, + expectedVersion: expectedVersion, + preferredPaths: preferredPaths) if status.isReady { self.rememberValidated(status) } @@ -197,13 +205,21 @@ enum CLIInstaller { } static func status(location: String) async -> Status { - await self.status( + let preferredPaths = await CommandResolver.preferredPathsAsync() + return await self.status( location: location, - expectedVersion: GatewayEnvironment.expectedGatewayVersionString()) + expectedVersion: GatewayEnvironment.expectedGatewayVersionString(), + preferredPaths: preferredPaths) } - private static func status(location: String, expectedVersion: String?) async -> Status { - let environment = self.probeEnvironment(location: location) + private static func status( + location: String, + expectedVersion: String?, + preferredPaths: [String]) async -> Status + { + let environment = self.probeEnvironment( + location: location, + preferredPaths: preferredPaths) let response = await ShellExecutor.runDetailed( command: [location, "--version"], cwd: nil, diff --git a/apps/macos/Sources/OpenClaw/CommandResolver.swift b/apps/macos/Sources/OpenClaw/CommandResolver.swift index 1a2216394d6f..4af3be446d05 100644 --- a/apps/macos/Sources/OpenClaw/CommandResolver.swift +++ b/apps/macos/Sources/OpenClaw/CommandResolver.swift @@ -81,6 +81,13 @@ enum CommandResolver { validatedExecutable: validatedExecutable) } + /// Version-manager trees can make discovery arbitrarily slow. Async callers + /// must leave their actor before touching the filesystem or the UI can stall. + @concurrent + static func preferredPathsAsync() async -> [String] { + self.preferredPaths() + } + static func preferredPaths( home: URL, current: [String],