From f61ae1193d0cbd40b6af5b9126d287c6354ca1e2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 25 Aug 2026 22:32:01 -0700 Subject: [PATCH] fix(macos): surface node service recovery commands (#129874) --- .../Sources/OpenClaw/NodeServiceManager.swift | 7 +++---- .../NodeServiceManagerTests.swift | 21 ++++++++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/macos/Sources/OpenClaw/NodeServiceManager.swift b/apps/macos/Sources/OpenClaw/NodeServiceManager.swift index 6771b664095c..42a197f3f85b 100644 --- a/apps/macos/Sources/OpenClaw/NodeServiceManager.swift +++ b/apps/macos/Sources/OpenClaw/NodeServiceManager.swift @@ -148,12 +148,11 @@ extension NodeServiceManager { private static func errorMessage(from result: CommandResult, treatNotLoadedAsError: Bool) -> String? { if !result.success { - return result.message ?? "Node service command failed" + return result.parsed.flatMap { + self.mergeHints(message: $0.error ?? $0.message, hints: $0.hints) + } ?? result.message ?? "Node service command failed" } guard let parsed = result.parsed else { return nil } - if parsed.ok == false { - return self.mergeHints(message: parsed.error ?? parsed.message, hints: parsed.hints) - } if treatNotLoadedAsError, parsed.result == "not-loaded" { let base = parsed.message ?? "Node service not loaded." return self.mergeHints(message: base, hints: parsed.hints) diff --git a/apps/macos/Tests/OpenClawIPCTests/NodeServiceManagerTests.swift b/apps/macos/Tests/OpenClawIPCTests/NodeServiceManagerTests.swift index 1327128106cc..44c06c5fa627 100644 --- a/apps/macos/Tests/OpenClawIPCTests/NodeServiceManagerTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/NodeServiceManagerTests.swift @@ -99,6 +99,8 @@ import Testing @Test(arguments: [ "failed-start", "failed-stop", "failed-restart", "json-success", "plain-success", "json-failure", + "json-failure-with-hints", "json-failure-with-hints-and-exit", "json-failure-hints-only", + "not-loaded-start", "not-loaded-stop", ]) func `node lifecycle respects process exit and optional JSON status`(_ scenario: String) async throws { let root = try makeTempDirForTests() @@ -116,6 +118,18 @@ import Testing case "$OPENCLAW_NODE_SERVICE_TEST_CASE" in failed-*) printf '{"ok":true}'; printf 'cleanup failed' >&2; exit 23 ;; json-failure) printf '{"ok":false,"error":"reported failure"}' ;; + json-failure-with-hints*) + printf '{"ok":false,"error":"Node service not installed.",' + printf '"hints":["openclaw node install","openclaw node start","third hint"]}' + if [ "$OPENCLAW_NODE_SERVICE_TEST_CASE" = "json-failure-with-hints-and-exit" ]; then exit 1; fi + ;; + json-failure-hints-only) + printf '{"ok":false,"hints":["openclaw node install","openclaw node start"]}' + ;; + not-loaded-*) + printf '{"ok":true,"result":"not-loaded","message":"Node service not loaded.",' + printf '"hints":["openclaw node install","openclaw node start"]}' + ;; plain-success) printf 'service started' ;; *) printf '{"ok":true}' ;; esac @@ -123,13 +137,18 @@ import Testing try script.write(to: executable, atomically: false, encoding: .utf8) let action = switch scenario { - case "failed-stop": "stop" + case "failed-stop", "not-loaded-stop": "stop" case "failed-restart": "restart" default: "start" } let expectedError: String? = switch scenario { case "failed-start", "failed-stop", "failed-restart": "cleanup failed" case "json-failure": "reported failure" + case "json-failure-with-hints", "json-failure-with-hints-and-exit": + "Node service not installed. (openclaw node install · openclaw node start)" + case "json-failure-hints-only": "openclaw node install · openclaw node start" + case "not-loaded-start": + "Node service not loaded. (openclaw node install · openclaw node start)" default: nil }