From 1e0c4ac453475b09d3b33885e352d3e410482d9f Mon Sep 17 00:00:00 2001 From: "Aryan Singh K." <70511529+aryansk@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:08:29 +0530 Subject: [PATCH 1/7] fix(messaging): preserve forward-list failures during cleanup Signed-off-by: Aryan Singh K. <70511529+aryansk@users.noreply.github.com> --- src/lib/onboard/agent-fixed-forward.ts | 2 +- src/lib/onboard/forward-cleanup.test.ts | 12 +++++++++++- src/lib/onboard/forward-cleanup.ts | 13 +++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/lib/onboard/agent-fixed-forward.ts b/src/lib/onboard/agent-fixed-forward.ts index bdfc46a754e..a9860a486de 100644 --- a/src/lib/onboard/agent-fixed-forward.ts +++ b/src/lib/onboard/agent-fixed-forward.ts @@ -29,7 +29,7 @@ export function ensureAgentFixedForward( const stopForwardForSandbox = (portToStop: string | number) => bestEffortForwardStopForSandbox( deps.runOpenshell, - (args, opts) => (deps.runCaptureOpenshell(args, opts) ?? "") as string, + (args, opts) => deps.runCaptureOpenshell(args, opts), portToStop, sandboxName, ); diff --git a/src/lib/onboard/forward-cleanup.test.ts b/src/lib/onboard/forward-cleanup.test.ts index c3ffc220080..cc06e1530e0 100644 --- a/src/lib/onboard/forward-cleanup.test.ts +++ b/src/lib/onboard/forward-cleanup.test.ts @@ -69,7 +69,7 @@ describe("bestEffortForwardStopForSandbox", () => { it("returns no-entry and runs a sandbox-scoped stop when no live forward is on that port", () => { const run = vi.fn(); - const fetch = vi.fn().mockReturnValue(forwardListWith([])); + const fetch = vi.fn().mockReturnValue(""); const outcome = bestEffortForwardStopForSandbox(run, fetch, 18789, "my-sandbox"); @@ -80,6 +80,16 @@ describe("bestEffortForwardStopForSandbox", () => { }); }); + it("returns list-failed and skips stop when the capture seam returns null", () => { + const run = vi.fn(); + const fetch = vi.fn().mockReturnValue(null); + + const outcome = bestEffortForwardStopForSandbox(run, fetch, 18789, "my-sandbox"); + + expect(outcome).toBe("list-failed"); + expect(run).not.toHaveBeenCalled(); + }); + it("skips the stop entirely when `forward list` itself throws (owner unknown)", () => { const run = vi.fn(); const fetch = vi.fn().mockImplementation(() => { diff --git a/src/lib/onboard/forward-cleanup.ts b/src/lib/onboard/forward-cleanup.ts index 5ccffe71f5d..93c03d9912c 100644 --- a/src/lib/onboard/forward-cleanup.ts +++ b/src/lib/onboard/forward-cleanup.ts @@ -13,7 +13,7 @@ export type ForwardStopRunner = ( export type ForwardListRunner = ( args: string[], opts: { ignoreError?: boolean; timeout?: number }, -) => string; +) => string | null; /** * `openshell forward stop ` — port-scoped, kills whatever forward is @@ -64,12 +64,12 @@ export function bestEffortForwardStopForSandbox( port: string | number, sandboxName: string, ): "stopped" | "owned-other" | "no-entry" | "list-failed" { - // Let runCaptureOpenshell throw on failure/timeout so the catch branch - // returns "list-failed". With ignoreError: true the runner would swallow - // the error and return "", which getOccupiedPorts parses as an empty map - // and the "no-entry" branch below would still run the stop — exactly the + // Let runCaptureOpenshell throw on failure/timeout, or return null, so the + // failure branch returns "list-failed". Treating either failure signal as + // an empty string would make getOccupiedPorts return an empty map and let + // the "no-entry" branch run a stop without ownership data — exactly the // collateral-damage case this helper exists to avoid. - let listOutput = ""; + let listOutput: string | null; try { listOutput = runCaptureOpenshell(["forward", "list"], { timeout: OPENSHELL_PROBE_TIMEOUT_MS, @@ -77,6 +77,7 @@ export function bestEffortForwardStopForSandbox( } catch { return "list-failed"; } + if (listOutput === null) return "list-failed"; const owner = getOccupiedPorts(listOutput).get(String(port)) ?? null; if (owner && owner !== sandboxName) { return "owned-other"; From f62cd66a43129ef4d20b14ef574788acc1b0ae8a Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 7 Aug 2026 01:35:04 -0700 Subject: [PATCH 2/7] test(messaging): cover failed forward list adapter --- .../messaging-host-forward-lifecycle.test.ts | 96 +++++++++++++++++++ .../messaging-host-forward-lifecycle.ts | 4 +- src/lib/onboard/forward-cleanup.test.ts | 6 -- 3 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts diff --git a/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts b/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts new file mode 100644 index 00000000000..01557554d91 --- /dev/null +++ b/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + captureOpenshell: vi.fn(), + runOpenshell: vi.fn(() => ({ status: 0 })), + runDetachedForwardStartWithRetries: vi.fn(), +})); + +vi.mock("../../adapters/openshell/runtime", () => ({ + captureOpenshell: mocks.captureOpenshell, + getOpenshellBinary: vi.fn(() => "/usr/bin/openshell"), + runOpenshell: mocks.runOpenshell, +})); + +vi.mock("../../core/wait", () => ({ sleepSeconds: vi.fn() })); + +vi.mock("../../onboard/forward-start", () => ({ + buildDetachedForwardStartSpawn: vi.fn(() => vi.fn()), + buildForwardStartProgressLogger: vi.fn(() => vi.fn()), + runDetachedForwardStartWithRetries: mocks.runDetachedForwardStartWithRetries, +})); + +import type { SandboxMessagingPlan } from "../../messaging/manifest"; +import { ensureMessagingHostForwardAfterRebuild } from "./messaging-host-forward-lifecycle"; + +function makePlan(): SandboxMessagingPlan { + return { + schemaVersion: 1, + sandboxName: "alpha", + agent: "openclaw", + workflow: "rebuild", + channels: [ + { + channelId: "teams", + displayName: "Microsoft Teams", + authMode: "token-paste", + active: true, + selected: true, + configured: true, + disabled: false, + inputs: [], + hooks: [], + hostForward: { + channelId: "teams", + port: 3978, + label: "Microsoft Teams webhook", + }, + }, + ], + disabledChannels: [], + credentialBindings: [], + networkPolicy: { presets: [], entries: [] }, + agentRender: [], + buildSteps: [], + stateUpdates: [], + healthChecks: [], + }; +} + +describe("ensureMessagingHostForwardAfterRebuild", () => { + beforeEach(() => { + vi.clearAllMocks(); + mocks.runDetachedForwardStartWithRetries.mockReturnValue({ ok: true, diagnostic: "" }); + }); + + it("skips cleanup stop when the messaging adapter cannot list forwards (#8522)", () => { + mocks.captureOpenshell.mockReturnValue({ status: 1, output: "" }); + + const result = ensureMessagingHostForwardAfterRebuild("alpha", makePlan()); + + expect(result).toBe(true); + expect(mocks.captureOpenshell).toHaveBeenNthCalledWith(1, ["forward", "list"], { + ignoreError: true, + }); + expect(mocks.captureOpenshell).toHaveBeenNthCalledWith(2, ["forward", "list"], { + ignoreError: true, + timeout: 15_000, + }); + expect(mocks.runOpenshell).not.toHaveBeenCalled(); + }); + + it("runs sandbox-scoped cleanup after a successful empty list (#8522)", () => { + mocks.captureOpenshell.mockReturnValue({ status: 0, output: "" }); + + const result = ensureMessagingHostForwardAfterRebuild("alpha", makePlan()); + + expect(result).toBe(true); + expect(mocks.runOpenshell).toHaveBeenCalledWith(["forward", "stop", "3978", "alpha"], { + ignoreError: true, + suppressOutput: true, + }); + }); +}); diff --git a/src/lib/actions/sandbox/messaging-host-forward-lifecycle.ts b/src/lib/actions/sandbox/messaging-host-forward-lifecycle.ts index 1e720a7ab5b..96cf9088560 100644 --- a/src/lib/actions/sandbox/messaging-host-forward-lifecycle.ts +++ b/src/lib/actions/sandbox/messaging-host-forward-lifecycle.ts @@ -18,7 +18,9 @@ import { parseForwardList } from "../../state/sandbox-session"; import { classifyForwardHealthWithReachability, isLocalForwardReachable } from "./forward-health"; function captureOpenShellOutput(args: string[], opts: Record = {}): string | null { - const result = captureOpenshell(args, opts as Parameters[1]); + const result = captureOpenshell(args, { ...opts, ignoreError: true } as Parameters< + typeof captureOpenshell + >[1]); return result.status === 0 ? result.output : null; } diff --git a/src/lib/onboard/forward-cleanup.test.ts b/src/lib/onboard/forward-cleanup.test.ts index cc06e1530e0..1b31a0d4496 100644 --- a/src/lib/onboard/forward-cleanup.test.ts +++ b/src/lib/onboard/forward-cleanup.test.ts @@ -41,12 +41,6 @@ describe("bestEffortForwardStopForSandbox", () => { ["forward", "list"], expect.objectContaining({ timeout: 15_000 }), ); - // Caller must NOT pass ignoreError; failures should throw so the catch - // branch returns "list-failed" instead of running a stop with no owner data. - expect(fetch).not.toHaveBeenCalledWith( - ["forward", "list"], - expect.objectContaining({ ignoreError: true }), - ); }); it("returns stopped and uses the sandbox-scoped forward stop form when ownership matches", () => { From 0827b0b7ac5d394a3767256b5848852d16565389 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 7 Aug 2026 01:39:23 -0700 Subject: [PATCH 3/7] test(messaging): clarify forward cleanup behavior --- .../actions/sandbox/messaging-host-forward-lifecycle.test.ts | 4 ++-- src/lib/onboard/forward-cleanup.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts b/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts index 01557554d91..209182b9ab6 100644 --- a/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts +++ b/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts @@ -66,7 +66,7 @@ describe("ensureMessagingHostForwardAfterRebuild", () => { mocks.runDetachedForwardStartWithRetries.mockReturnValue({ ok: true, diagnostic: "" }); }); - it("skips cleanup stop when the messaging adapter cannot list forwards (#8522)", () => { + it("skips the sandbox-scoped stop when OpenShell cannot list forwards (#8522)", () => { mocks.captureOpenshell.mockReturnValue({ status: 1, output: "" }); const result = ensureMessagingHostForwardAfterRebuild("alpha", makePlan()); @@ -82,7 +82,7 @@ describe("ensureMessagingHostForwardAfterRebuild", () => { expect(mocks.runOpenshell).not.toHaveBeenCalled(); }); - it("runs sandbox-scoped cleanup after a successful empty list (#8522)", () => { + it("runs the sandbox-scoped stop when OpenShell returns an empty forward list (#8522)", () => { mocks.captureOpenshell.mockReturnValue({ status: 0, output: "" }); const result = ensureMessagingHostForwardAfterRebuild("alpha", makePlan()); diff --git a/src/lib/onboard/forward-cleanup.test.ts b/src/lib/onboard/forward-cleanup.test.ts index 1b31a0d4496..3a09ac009a4 100644 --- a/src/lib/onboard/forward-cleanup.test.ts +++ b/src/lib/onboard/forward-cleanup.test.ts @@ -74,7 +74,7 @@ describe("bestEffortForwardStopForSandbox", () => { }); }); - it("returns list-failed and skips stop when the capture seam returns null", () => { + it("returns list-failed and skips the stop when the forward-list runner returns null (#8522)", () => { const run = vi.fn(); const fetch = vi.fn().mockReturnValue(null); From 22d93c23b5fd3450f39016b8dc059a6636027ca9 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 7 Aug 2026 01:54:14 -0700 Subject: [PATCH 4/7] fix(messaging): preserve forward list startup failures --- .../messaging-host-forward-lifecycle.test.ts | 14 ++++++++++++-- src/lib/onboard/agent-fixed-forward.ts | 10 +++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts b/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts index 209182b9ab6..7ad3089ef76 100644 --- a/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts +++ b/src/lib/actions/sandbox/messaging-host-forward-lifecycle.test.ts @@ -66,12 +66,18 @@ describe("ensureMessagingHostForwardAfterRebuild", () => { mocks.runDetachedForwardStartWithRetries.mockReturnValue({ ok: true, diagnostic: "" }); }); - it("skips the sandbox-scoped stop when OpenShell cannot list forwards (#8522)", () => { + it("does not verify startup when OpenShell cannot list forwards (#8522)", () => { mocks.captureOpenshell.mockReturnValue({ status: 1, output: "" }); + mocks.runDetachedForwardStartWithRetries.mockImplementation( + (_runDetachedSpawn, fetchForwardList: () => string) => { + expect(() => fetchForwardList()).toThrow("OpenShell forward list failed"); + return { ok: false, diagnostic: "ownership query failed" }; + }, + ); const result = ensureMessagingHostForwardAfterRebuild("alpha", makePlan()); - expect(result).toBe(true); + expect(result).toBe(false); expect(mocks.captureOpenshell).toHaveBeenNthCalledWith(1, ["forward", "list"], { ignoreError: true, }); @@ -79,6 +85,10 @@ describe("ensureMessagingHostForwardAfterRebuild", () => { ignoreError: true, timeout: 15_000, }); + expect(mocks.captureOpenshell).toHaveBeenNthCalledWith(3, ["forward", "list"], { + ignoreError: true, + timeout: 15_000, + }); expect(mocks.runOpenshell).not.toHaveBeenCalled(); }); diff --git a/src/lib/onboard/agent-fixed-forward.ts b/src/lib/onboard/agent-fixed-forward.ts index a9860a486de..3bb214a2c0f 100644 --- a/src/lib/onboard/agent-fixed-forward.ts +++ b/src/lib/onboard/agent-fixed-forward.ts @@ -39,9 +39,13 @@ export function ensureAgentFixedForward( buildDetachedForwardStartSpawn( deps.openshellArgv(["forward", "start", "--background", forwardTarget, sandboxName]), ), - () => - (deps.runCaptureOpenshell(["forward", "list"], { timeout: OPENSHELL_PROBE_TIMEOUT_MS }) ?? - "") as string, + () => { + const output = deps.runCaptureOpenshell(["forward", "list"], { + timeout: OPENSHELL_PROBE_TIMEOUT_MS, + }); + if (output === null) throw new Error("OpenShell forward list failed"); + return output; + }, { port, sandboxName }, () => { deps.sleep(1); From 3ce715fd202ffdb6cb1e54f75fcb6e15216e5293 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 7 Aug 2026 01:58:56 -0700 Subject: [PATCH 5/7] docs(messaging): clarify forward ownership failure --- src/lib/onboard/forward-cleanup.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib/onboard/forward-cleanup.ts b/src/lib/onboard/forward-cleanup.ts index 93c03d9912c..e00fc33ae50 100644 --- a/src/lib/onboard/forward-cleanup.ts +++ b/src/lib/onboard/forward-cleanup.ts @@ -64,11 +64,9 @@ export function bestEffortForwardStopForSandbox( port: string | number, sandboxName: string, ): "stopped" | "owned-other" | "no-entry" | "list-failed" { - // Let runCaptureOpenshell throw on failure/timeout, or return null, so the - // failure branch returns "list-failed". Treating either failure signal as - // an empty string would make getOccupiedPorts return an empty map and let - // the "no-entry" branch run a stop without ownership data — exactly the - // collateral-damage case this helper exists to avoid. + // A thrown error or `null` means that OpenShell did not return ownership data. + // Preserve either result as `list-failed`. Converting it to an empty string + // would enter the `no-entry` cleanup path. let listOutput: string | null; try { listOutput = runCaptureOpenshell(["forward", "list"], { From b8b366425c612dc3343587b7aa31dc31c39b9ad7 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 7 Aug 2026 02:03:04 -0700 Subject: [PATCH 6/7] fix(rebuild): report unverified messaging forward cause --- src/lib/actions/sandbox/rebuild-finalization.test.ts | 4 ++++ src/lib/actions/sandbox/rebuild-finalization.ts | 2 +- src/lib/actions/sandbox/rebuild-post-restore-phase.test.ts | 4 ++++ src/lib/actions/sandbox/rebuild-post-restore-phase.ts | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/actions/sandbox/rebuild-finalization.test.ts b/src/lib/actions/sandbox/rebuild-finalization.test.ts index 287eae87493..7f812ca19b8 100644 --- a/src/lib/actions/sandbox/rebuild-finalization.test.ts +++ b/src/lib/actions/sandbox/rebuild-finalization.test.ts @@ -125,6 +125,10 @@ describe("finalizeRebuildPostRestore", () => { expect(output).toContain("Mutable config permissions were not verified"); expect(output).toContain("Mutable OpenClaw config hash was not refreshed"); expect(output).toContain("Messaging webhook forward was not verified"); + expect(output).toContain( + "Resolve the preceding OpenShell or port error, then run `nemoclaw alpha connect`.", + ); + expect(output).not.toContain("after resolving the port conflict"); expect(output).toContain("Policy presets failed to reapply: messaging-telegram"); expect(output).toContain("Shields were previously enabled"); const orderedFragments = [ diff --git a/src/lib/actions/sandbox/rebuild-finalization.ts b/src/lib/actions/sandbox/rebuild-finalization.ts index 511e672c094..8ea93441d34 100644 --- a/src/lib/actions/sandbox/rebuild-finalization.ts +++ b/src/lib/actions/sandbox/rebuild-finalization.ts @@ -143,7 +143,7 @@ export function finalizeRebuildPostRestore( } if (messagingHostForwardUnverified) { writeLine( - ` Messaging webhook forward was not verified \u2014 run \`${CLI_NAME} ${options.sandboxName} connect\` after resolving the port conflict`, + ` Messaging webhook forward was not verified. Resolve the preceding OpenShell or port error, then run \`${CLI_NAME} ${options.sandboxName} connect\`.`, ); } if (policyPresetRestoreIncomplete) { diff --git a/src/lib/actions/sandbox/rebuild-post-restore-phase.test.ts b/src/lib/actions/sandbox/rebuild-post-restore-phase.test.ts index 4933a2c7d78..2e16dd6ca43 100644 --- a/src/lib/actions/sandbox/rebuild-post-restore-phase.test.ts +++ b/src/lib/actions/sandbox/rebuild-post-restore-phase.test.ts @@ -185,6 +185,10 @@ describe("rebuild post-restore phase", () => { const output = vi.mocked(console.log).mock.calls.flat().join("\n"); expect(args.bail).not.toHaveBeenCalled(); expect(output).toContain("rebuilt but some post-restore steps were incomplete"); + expect(output).toContain( + "Resolve the preceding OpenShell or port error, then run `nemoclaw alpha connect`.", + ); + expect(output).not.toContain("after resolving the port conflict"); expect(output).toContain("Hermes API bearer token changed during rebuild"); expect(output).toContain("nemoclaw alpha gateway-token --quiet"); }); diff --git a/src/lib/actions/sandbox/rebuild-post-restore-phase.ts b/src/lib/actions/sandbox/rebuild-post-restore-phase.ts index 99ddcf85d5d..9704f6083fa 100644 --- a/src/lib/actions/sandbox/rebuild-post-restore-phase.ts +++ b/src/lib/actions/sandbox/rebuild-post-restore-phase.ts @@ -326,7 +326,7 @@ export async function runRebuildPostRestorePhase( } if (messagingHostForwardUnverified) { console.log( - ` Messaging webhook forward was not verified \u2014 run \`${CLI_NAME} ${sandboxName} connect\` after resolving the port conflict`, + ` Messaging webhook forward was not verified. Resolve the preceding OpenShell or port error, then run \`${CLI_NAME} ${sandboxName} connect\`.`, ); } printHermesGatewayRestoreRecovery(sandboxName, hermesGatewayRestoreState); From db85a5bcdc49480a201122c80b355878ccb28c1a Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Fri, 7 Aug 2026 02:07:20 -0700 Subject: [PATCH 7/7] docs(rebuild): split messaging recovery steps --- src/lib/actions/sandbox/rebuild-finalization.test.ts | 2 +- src/lib/actions/sandbox/rebuild-finalization.ts | 2 +- src/lib/actions/sandbox/rebuild-post-restore-phase.test.ts | 2 +- src/lib/actions/sandbox/rebuild-post-restore-phase.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/actions/sandbox/rebuild-finalization.test.ts b/src/lib/actions/sandbox/rebuild-finalization.test.ts index 7f812ca19b8..03910f3b03f 100644 --- a/src/lib/actions/sandbox/rebuild-finalization.test.ts +++ b/src/lib/actions/sandbox/rebuild-finalization.test.ts @@ -126,7 +126,7 @@ describe("finalizeRebuildPostRestore", () => { expect(output).toContain("Mutable OpenClaw config hash was not refreshed"); expect(output).toContain("Messaging webhook forward was not verified"); expect(output).toContain( - "Resolve the preceding OpenShell or port error, then run `nemoclaw alpha connect`.", + "Correct the preceding OpenShell error or port conflict. Then run `nemoclaw alpha connect`.", ); expect(output).not.toContain("after resolving the port conflict"); expect(output).toContain("Policy presets failed to reapply: messaging-telegram"); diff --git a/src/lib/actions/sandbox/rebuild-finalization.ts b/src/lib/actions/sandbox/rebuild-finalization.ts index 8ea93441d34..53a35c957ac 100644 --- a/src/lib/actions/sandbox/rebuild-finalization.ts +++ b/src/lib/actions/sandbox/rebuild-finalization.ts @@ -143,7 +143,7 @@ export function finalizeRebuildPostRestore( } if (messagingHostForwardUnverified) { writeLine( - ` Messaging webhook forward was not verified. Resolve the preceding OpenShell or port error, then run \`${CLI_NAME} ${options.sandboxName} connect\`.`, + ` Messaging webhook forward was not verified. Correct the preceding OpenShell error or port conflict. Then run \`${CLI_NAME} ${options.sandboxName} connect\`.`, ); } if (policyPresetRestoreIncomplete) { diff --git a/src/lib/actions/sandbox/rebuild-post-restore-phase.test.ts b/src/lib/actions/sandbox/rebuild-post-restore-phase.test.ts index 2e16dd6ca43..e13099f3beb 100644 --- a/src/lib/actions/sandbox/rebuild-post-restore-phase.test.ts +++ b/src/lib/actions/sandbox/rebuild-post-restore-phase.test.ts @@ -186,7 +186,7 @@ describe("rebuild post-restore phase", () => { expect(args.bail).not.toHaveBeenCalled(); expect(output).toContain("rebuilt but some post-restore steps were incomplete"); expect(output).toContain( - "Resolve the preceding OpenShell or port error, then run `nemoclaw alpha connect`.", + "Correct the preceding OpenShell error or port conflict. Then run `nemoclaw alpha connect`.", ); expect(output).not.toContain("after resolving the port conflict"); expect(output).toContain("Hermes API bearer token changed during rebuild"); diff --git a/src/lib/actions/sandbox/rebuild-post-restore-phase.ts b/src/lib/actions/sandbox/rebuild-post-restore-phase.ts index 9704f6083fa..78bb641a01c 100644 --- a/src/lib/actions/sandbox/rebuild-post-restore-phase.ts +++ b/src/lib/actions/sandbox/rebuild-post-restore-phase.ts @@ -326,7 +326,7 @@ export async function runRebuildPostRestorePhase( } if (messagingHostForwardUnverified) { console.log( - ` Messaging webhook forward was not verified. Resolve the preceding OpenShell or port error, then run \`${CLI_NAME} ${sandboxName} connect\`.`, + ` Messaging webhook forward was not verified. Correct the preceding OpenShell error or port conflict. Then run \`${CLI_NAME} ${sandboxName} connect\`.`, ); } printHermesGatewayRestoreRecovery(sandboxName, hermesGatewayRestoreState);