From aabf68606a5e4da43e975136d7376752d77ffffd Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Mon, 6 Jul 2026 05:00:43 -0700 Subject: [PATCH] fix(macos): preserve launchd Node gateway in PortGuardian (#100867) * fix(macos): preserve launchd Node gateway in PortGuardian Co-authored-by: lsr911 <18230762+lsr911@users.noreply.github.com> * fix(macos): tighten PortGuardian gateway matcher --------- Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com> Co-authored-by: lsr911 <18230762+lsr911@users.noreply.github.com> --- .../macos/Sources/OpenClaw/PortGuardian.swift | 25 +++++++++ .../PortGuardianIsExpectedTests.swift | 53 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 apps/macos/Tests/OpenClawIPCTests/PortGuardianIsExpectedTests.swift diff --git a/apps/macos/Sources/OpenClaw/PortGuardian.swift b/apps/macos/Sources/OpenClaw/PortGuardian.swift index 383065a8d114..c7db8ba2054f 100644 --- a/apps/macos/Sources/OpenClaw/PortGuardian.swift +++ b/apps/macos/Sources/OpenClaw/PortGuardian.swift @@ -534,6 +534,7 @@ actor PortGuardian { { return true } + if self.isNodeOpenClawGatewayCommand(full) { return true } // If args are unavailable, treat a CLI listener as expected. if cmd.contains("openclaw"), full == cmd { return true } return false @@ -542,6 +543,30 @@ actor PortGuardian { } } + private static func isNodeOpenClawGatewayCommand(_ fullCommand: String) -> Bool { + let tokens = fullCommand + .split(whereSeparator: \.isWhitespace) + .map { self.unquoteCommandToken(String($0)) } + guard tokens.count >= 3 else { return false } + guard URL(fileURLWithPath: tokens[0]).lastPathComponent.lowercased() == "node" else { + return false + } + return self.isOpenClawDistEntrypointToken(tokens[1]) + && tokens[2].lowercased() == "gateway" + } + + private static func isOpenClawDistEntrypointToken(_ token: String) -> Bool { + let normalized = token.replacingOccurrences(of: "\\", with: "/").lowercased() + guard normalized.hasSuffix("/dist/index.js") else { return false } + return normalized + .split(separator: "/", omittingEmptySubsequences: true) + .contains("openclaw") + } + + private static func unquoteCommandToken(_ token: String) -> String { + token.trimmingCharacters(in: CharacterSet(charactersIn: "\"'")) + } + private func probeGatewayHealthIfNeeded( port: Int, mode: AppState.ConnectionMode, diff --git a/apps/macos/Tests/OpenClawIPCTests/PortGuardianIsExpectedTests.swift b/apps/macos/Tests/OpenClawIPCTests/PortGuardianIsExpectedTests.swift new file mode 100644 index 000000000000..a95cbd17303f --- /dev/null +++ b/apps/macos/Tests/OpenClawIPCTests/PortGuardianIsExpectedTests.swift @@ -0,0 +1,53 @@ +import Foundation +import Testing +@testable import OpenClaw + +struct PortGuardianIsExpectedTests { + @Test func `local mode preserves launchd node dist gateway command`() { + let fullCommand = """ + /opt/homebrew/bin/node /opt/homebrew/lib/node_modules/openclaw/dist/index.js gateway --port 18789 --bind loopback + """ + + #expect(PortGuardian._testIsExpected( + command: "node", + fullCommand: fullCommand, + port: 18789, + mode: .local)) + } + + @Test func `local mode preserves git checkout node dist gateway command`() { + let fullCommand = """ + /usr/local/bin/node /Users/dev/Projects/openclaw/dist/index.js gateway --port 18789 + """ + + #expect(PortGuardian._testIsExpected( + command: "node", + fullCommand: fullCommand, + port: 18789, + mode: .local)) + } + + @Test func `local mode rejects similarly named node project`() { + #expect(!PortGuardian._testIsExpected( + command: "node", + fullCommand: "/usr/local/bin/node /tmp/openclaw-tools/dist/index.js gateway --port 18789", + port: 18789, + mode: .local)) + } + + @Test func `local mode rejects gateway appearing after another node argument`() { + #expect(!PortGuardian._testIsExpected( + command: "node", + fullCommand: "/usr/local/bin/node --inspect /tmp/openclaw/dist/index.js gateway --port 18789", + port: 18789, + mode: .local)) + } + + @Test func `local mode rejects node dist entrypoint without gateway subcommand`() { + #expect(!PortGuardian._testIsExpected( + command: "node", + fullCommand: "/opt/homebrew/bin/node /opt/homebrew/lib/node_modules/openclaw/dist/index.js doctor", + port: 18789, + mode: .local)) + } +}