From 0fbaef799f9163b37eb2c42c219b9320d7cff308 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 9 Apr 2026 03:52:59 +0100 Subject: [PATCH] fix(macos): stabilize shell timeouts and command resolution tests --- .../Sources/OpenClaw/CommandResolver.swift | 11 ++++--- .../Sources/OpenClaw/ShellExecutor.swift | 6 ++-- .../CommandResolverTests.swift | 33 +++++++++++-------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/apps/macos/Sources/OpenClaw/CommandResolver.swift b/apps/macos/Sources/OpenClaw/CommandResolver.swift index cacfac2f068..06e5d5c32dc 100644 --- a/apps/macos/Sources/OpenClaw/CommandResolver.swift +++ b/apps/macos/Sources/OpenClaw/CommandResolver.swift @@ -235,7 +235,8 @@ enum CommandResolver { extraArgs: [String] = [], defaults: UserDefaults = .standard, configRoot: [String: Any]? = nil, - searchPaths: [String]? = nil) -> [String] + searchPaths: [String]? = nil, + projectRoot: URL? = nil) -> [String] { let settings = self.connectionSettings(defaults: defaults, configRoot: configRoot) if settings.mode == .remote, let ssh = self.sshNodeCommand( @@ -246,7 +247,7 @@ enum CommandResolver { return ssh } - let root = self.projectRoot() + let root = projectRoot ?? self.projectRoot() if let openclawPath = self.projectOpenClawExecutable(projectRoot: root) { return [openclawPath, subcommand] + extraArgs } @@ -289,14 +290,16 @@ enum CommandResolver { extraArgs: [String] = [], defaults: UserDefaults = .standard, configRoot: [String: Any]? = nil, - searchPaths: [String]? = nil) -> [String] + searchPaths: [String]? = nil, + projectRoot: URL? = nil) -> [String] { self.openclawNodeCommand( subcommand: subcommand, extraArgs: extraArgs, defaults: defaults, configRoot: configRoot, - searchPaths: searchPaths) + searchPaths: searchPaths, + projectRoot: projectRoot) } // MARK: - SSH helpers diff --git a/apps/macos/Sources/OpenClaw/ShellExecutor.swift b/apps/macos/Sources/OpenClaw/ShellExecutor.swift index ec757441a15..e275b03c696 100644 --- a/apps/macos/Sources/OpenClaw/ShellExecutor.swift +++ b/apps/macos/Sources/OpenClaw/ShellExecutor.swift @@ -73,8 +73,10 @@ enum ShellExecutor { group.addTask { await waitTask.value } group.addTask { try? await Task.sleep(nanoseconds: nanos) - if process.isRunning { process.terminate() } - _ = await waitTask.value // drain pipes after termination + guard process.isRunning else { + return await waitTask.value + } + process.terminate() return ShellResult( stdout: "", stderr: "", diff --git a/apps/macos/Tests/OpenClawIPCTests/CommandResolverTests.swift b/apps/macos/Tests/OpenClawIPCTests/CommandResolverTests.swift index 5e8e68f52e6..bb5d0b14f28 100644 --- a/apps/macos/Tests/OpenClawIPCTests/CommandResolverTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/CommandResolverTests.swift @@ -17,7 +17,6 @@ import Testing private func makeProjectRootWithPnpm() throws -> (tmp: URL, pnpmPath: URL) { let tmp = try makeTempDirForTests() - CommandResolver.setProjectRoot(tmp.path) let pnpmPath = tmp.appendingPathComponent("node_modules/.bin/pnpm") try makeExecutableForTests(at: pnpmPath) return (tmp, pnpmPath) @@ -27,12 +26,17 @@ import Testing let defaults = self.makeLocalDefaults() let tmp = try makeTempDirForTests() - CommandResolver.setProjectRoot(tmp.path) let openclawPath = tmp.appendingPathComponent("node_modules/.bin/openclaw") try makeExecutableForTests(at: openclawPath) - let cmd = CommandResolver.openclawCommand(subcommand: "gateway", defaults: defaults, configRoot: [:]) + let searchPaths = [tmp.appendingPathComponent("node_modules/.bin").path] + let cmd = CommandResolver.openclawCommand( + subcommand: "gateway", + defaults: defaults, + configRoot: [:], + searchPaths: searchPaths, + projectRoot: tmp) #expect(cmd.prefix(2).elementsEqual([openclawPath.path, "gateway"])) } @@ -40,7 +44,6 @@ import Testing let defaults = self.makeLocalDefaults() let tmp = try makeTempDirForTests() - CommandResolver.setProjectRoot(tmp.path) let nodePath = tmp.appendingPathComponent("node_modules/.bin/node") let scriptPath = tmp.appendingPathComponent("bin/openclaw.js") @@ -53,7 +56,8 @@ import Testing subcommand: "rpc", defaults: defaults, configRoot: [:], - searchPaths: [tmp.appendingPathComponent("node_modules/.bin").path]) + searchPaths: [tmp.appendingPathComponent("node_modules/.bin").path], + projectRoot: tmp) #expect(cmd.count >= 3) if cmd.count >= 3 { @@ -67,7 +71,6 @@ import Testing let defaults = self.makeLocalDefaults() let tmp = try makeTempDirForTests() - CommandResolver.setProjectRoot(tmp.path) let binDir = tmp.appendingPathComponent("bin") let openclawPath = binDir.appendingPathComponent("openclaw") @@ -79,7 +82,8 @@ import Testing subcommand: "rpc", defaults: defaults, configRoot: [:], - searchPaths: [binDir.path]) + searchPaths: [binDir.path], + projectRoot: tmp) #expect(cmd.prefix(2).elementsEqual([openclawPath.path, "rpc"])) } @@ -88,7 +92,6 @@ import Testing let defaults = self.makeLocalDefaults() let tmp = try makeTempDirForTests() - CommandResolver.setProjectRoot(tmp.path) let binDir = tmp.appendingPathComponent("bin") let openclawPath = binDir.appendingPathComponent("openclaw") @@ -98,7 +101,8 @@ import Testing subcommand: "gateway", defaults: defaults, configRoot: [:], - searchPaths: [binDir.path]) + searchPaths: [binDir.path], + projectRoot: tmp) #expect(cmd.prefix(2).elementsEqual([openclawPath.path, "gateway"])) } @@ -133,9 +137,11 @@ import Testing @Test func `preferred paths start with project node bins`() throws { let tmp = try makeTempDirForTests() - CommandResolver.setProjectRoot(tmp.path) - let first = CommandResolver.preferredPaths().first + let first = CommandResolver.preferredPaths( + home: FileManager().homeDirectoryForCurrentUser, + current: [], + projectRoot: tmp).first #expect(first == tmp.appendingPathComponent("node_modules/.bin").path) } @@ -182,7 +188,6 @@ import Testing defaults.set("openclaw@example.com:2222", forKey: remoteTargetKey) let tmp = try makeTempDirForTests() - CommandResolver.setProjectRoot(tmp.path) let openclawPath = tmp.appendingPathComponent("node_modules/.bin/openclaw") try makeExecutableForTests(at: openclawPath) @@ -190,7 +195,9 @@ import Testing let cmd = CommandResolver.openclawCommand( subcommand: "daemon", defaults: defaults, - configRoot: ["gateway": ["mode": "local"]]) + configRoot: ["gateway": ["mode": "local"]], + searchPaths: [tmp.appendingPathComponent("node_modules/.bin").path], + projectRoot: tmp) #expect(cmd.first == openclawPath.path) #expect(cmd.count >= 2)