From a00c58363a4f13bfc9abbfde41063c1932e04fa3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 22 May 2026 13:52:21 +0100 Subject: [PATCH] fix(cli): suppress systemd hints for live gateway (#85336) * fix(cli): suppress systemd hints for live gateway * test(cli): type systemd hint mock --- CHANGELOG.md | 1 + src/cli/daemon-cli/status.print.test.ts | 47 +++++++++++++++++++++++-- src/cli/daemon-cli/status.print.ts | 4 ++- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f99b0ab2b88..da20c62ff69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Docs: https://docs.openclaw.ai - Agents/OpenAI: preserve structured provider error code, type, and redacted body metadata on boundary-aware transport failures. - Doctor/Codex: point native Codex asset warnings at the canonical `openclaw migrate plan codex` preview command. Fixes #84948. Thanks @markoa. - CLI/models: make `capability model auth logout --agent` remove auth profiles from the selected non-default agent store. Fixes #85092. Thanks @islandpreneur007. +- CLI/status: suppress systemd user-service setup hints when `openclaw status --deep` can already reach a running Gateway RPC service. Fixes #85094. Thanks @islandpreneur007. - CLI/agents: retry transient normal-close Gateway handshakes before falling back to embedded `openclaw agent` execution. - CLI/update: keep managed Gateway service stop/restart status lines out of `openclaw update --json` stdout so package-update automation can parse the JSON payload. - Plugins: resolve OpenClaw plugin SDK subpaths for native external plugin runtimes without mutating package installs or broadening process-wide module resolution. diff --git a/src/cli/daemon-cli/status.print.test.ts b/src/cli/daemon-cli/status.print.test.ts index 7b15de5338e..aa13ba9ac26 100644 --- a/src/cli/daemon-cli/status.print.test.ts +++ b/src/cli/daemon-cli/status.print.test.ts @@ -9,6 +9,8 @@ const runtime = vi.hoisted(() => ({ const resolveControlUiLinksMock = vi.hoisted(() => vi.fn((_opts?: unknown) => ({ httpUrl: "http://127.0.0.1:18789" })), ); +const isSystemdUnavailableDetailMock = vi.hoisted(() => vi.fn(() => false)); +const renderSystemdUnavailableHintsMock = vi.hoisted(() => vi.fn<() => string[]>(() => [])); vi.mock("../../runtime.js", () => ({ defaultRuntime: runtime, @@ -46,8 +48,8 @@ vi.mock("../../daemon/restart-logs.js", () => ({ })); vi.mock("../../daemon/systemd-hints.js", () => ({ - isSystemdUnavailableDetail: () => false, - renderSystemdUnavailableHints: () => [], + isSystemdUnavailableDetail: isSystemdUnavailableDetailMock, + renderSystemdUnavailableHints: renderSystemdUnavailableHintsMock, })); vi.mock("../../infra/wsl.js", () => ({ @@ -87,6 +89,8 @@ describe("printDaemonStatus", () => { runtime.log.mockReset(); runtime.error.mockReset(); resolveControlUiLinksMock.mockClear(); + isSystemdUnavailableDetailMock.mockReset().mockReturnValue(false); + renderSystemdUnavailableHintsMock.mockReset().mockReturnValue([]); }); it("prints stale gateway pid guidance when runtime does not own the listener", () => { @@ -506,4 +510,43 @@ describe("printDaemonStatus", () => { expectMockLineContains(runtime.log, "ai.openclaw.gateway.rescue"); expect(runtime.error).not.toHaveBeenCalled(); }); + + it("does not print systemd user-service hints when a gateway responds", () => { + const platform = vi.spyOn(process, "platform", "get").mockReturnValue("linux"); + isSystemdUnavailableDetailMock.mockReturnValue(true); + renderSystemdUnavailableHintsMock.mockReturnValue(["run loginctl enable-linger"]); + + try { + printDaemonStatus( + { + service: { + label: "systemd user", + loaded: false, + loadedText: "not loaded", + notLoadedText: "not loaded", + runtime: { status: "unknown", detail: "systemd user services unavailable" }, + }, + rpc: { + ok: true, + url: "ws://127.0.0.1:18789", + server: { version: "2026.5.12" }, + }, + port: { + port: 18789, + status: "busy", + listeners: [], + hints: [], + }, + extraServices: [], + }, + { json: false }, + ); + } finally { + platform.mockRestore(); + } + + const errors = runtime.error.mock.calls.map(([line]) => line).join("\n"); + expect(errors).not.toContain("systemd user services unavailable"); + expect(errors).not.toContain("run loginctl enable-linger"); + }); }); diff --git a/src/cli/daemon-cli/status.print.ts b/src/cli/daemon-cli/status.print.ts index e58ba8bd3f6..d33e59abcc9 100644 --- a/src/cli/daemon-cli/status.print.ts +++ b/src/cli/daemon-cli/status.print.ts @@ -320,7 +320,9 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) } const systemdUnavailable = - process.platform === "linux" && isSystemdUnavailableDetail(service.runtime?.detail); + process.platform === "linux" && + rpc?.ok !== true && + isSystemdUnavailableDetail(service.runtime?.detail); if (systemdUnavailable) { const container = Boolean( resolveDaemonContainerContext(service.command?.environment ?? process.env),