Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/lib/share-command-deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface ShareCommandDeps {
ensureLive: (sandboxName: string) => Promise<void>;
/**
* Check whether `remotePath` exists inside the sandbox via
* `openshell sandbox exec <name> -- test -e <remotePath>`. Returns true when
* `openshell sandbox exec -n <name> -- test -e <remotePath>`. Returns true when
* the path exists; false when it is missing, when the sandbox is unreachable,
* or when the exec itself fails. Used by `share mount` as a pre-flight
* before invoking `sshfs`, which exits non-zero with empty stderr on a
Expand Down Expand Up @@ -49,7 +49,7 @@ export function buildShareCommandDeps(): ShareCommandDeps {
},
checkSandboxPathExists: (sandboxName: string, remotePath: string) => {
const result = captureOpenshell(
["sandbox", "exec", sandboxName, "--", "test", "-e", remotePath],
["sandbox", "exec", "-n", sandboxName, "--", "test", "-e", remotePath],
{ ignoreError: true, timeout: OPENSHELL_PROBE_TIMEOUT_MS },
);
return result.status === 0;
Expand Down
10 changes: 5 additions & 5 deletions test/e2e/validation_suites/lib/rebuild_upgrade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ rebuild_upgrade_assert_sandbox_reachable() {
fi
local sandbox
sandbox="$(_rebuild_upgrade_ctx E2E_SANDBOX_NAME)"
if _rebuild_upgrade_run REBUILD_UPGRADE_SANDBOX_CMD openshell sandbox exec "${sandbox}" -- true; then
if _rebuild_upgrade_run REBUILD_UPGRADE_SANDBOX_CMD openshell sandbox exec -n "${sandbox}" -- true; then
e2e_pass "suite.upgrade.survivor_agent_reachable"
else
e2e_fail "suite.upgrade.survivor_agent_reachable"
Expand All @@ -55,7 +55,7 @@ rebuild_upgrade_assert_marker_preserved() {
sandbox="$(_rebuild_upgrade_ctx E2E_SANDBOX_NAME)"
marker_path="${E2E_REBUILD_MARKER_PATH:-/workspace/.nemoclaw-rebuild-marker}"
expected="${E2E_REBUILD_MARKER_EXPECTED:-${E2E_STATE_MARKER_EXPECTED:-}}"
actual="$(_rebuild_upgrade_run REBUILD_UPGRADE_SANDBOX_CMD openshell sandbox exec "${sandbox}" -- cat "${marker_path}" 2>/dev/null || true)"
actual="$(_rebuild_upgrade_run REBUILD_UPGRADE_SANDBOX_CMD openshell sandbox exec -n "${sandbox}" -- cat "${marker_path}" 2>/dev/null || true)"
if [[ -n "${actual}" && (-z "${expected}" || "${actual}" == "${expected}") ]]; then
e2e_pass "suite.rebuild.workspace_state_preserved"
else
Expand All @@ -74,7 +74,7 @@ rebuild_upgrade_assert_agent_version_upgraded() {
old="${E2E_OLD_AGENT_VERSION:-}"
expected="${E2E_EXPECTED_AGENT_VERSION:-}"
cmd="${E2E_AGENT_VERSION_COMMAND:-openclaw --version}"
actual="$(_rebuild_upgrade_run REBUILD_UPGRADE_SANDBOX_CMD openshell sandbox exec "${sandbox}" -- bash -lc "${cmd}" 2>/dev/null || true)"
actual="$(_rebuild_upgrade_run REBUILD_UPGRADE_SANDBOX_CMD openshell sandbox exec -n "${sandbox}" -- bash -lc "${cmd}" 2>/dev/null || true)"
if [[ -n "${actual}" && (-z "${old}" || "${actual}" != *"${old}"*) && (-z "${expected}" || "${actual}" == *"${expected}"*) ]]; then
e2e_pass "suite.rebuild.agent_version_upgraded"
else
Expand All @@ -91,7 +91,7 @@ rebuild_upgrade_assert_inference_works() {
local sandbox cmd output
sandbox="$(_rebuild_upgrade_ctx E2E_SANDBOX_NAME)"
cmd="${E2E_INFERENCE_CHECK_COMMAND:-curl -fsS http://inference.local/v1/models}"
output="$(_rebuild_upgrade_run REBUILD_UPGRADE_SANDBOX_CMD openshell sandbox exec "${sandbox}" -- bash -lc "${cmd}" 2>/dev/null || true)"
output="$(_rebuild_upgrade_run REBUILD_UPGRADE_SANDBOX_CMD openshell sandbox exec -n "${sandbox}" -- bash -lc "${cmd}" 2>/dev/null || true)"
if [[ -n "${output}" ]]; then
e2e_pass "suite.rebuild.inference_still_works"
else
Expand Down Expand Up @@ -129,7 +129,7 @@ rebuild_upgrade_assert_hermes_config_preserved() {
fi
local sandbox output
sandbox="$(_rebuild_upgrade_ctx E2E_SANDBOX_NAME)"
output="$(_rebuild_upgrade_run REBUILD_UPGRADE_SANDBOX_CMD openshell sandbox exec "${sandbox}" -- bash -lc "grep -R 'platforms.discord\|DISCORD' ~/.hermes . 2>/dev/null" || true)"
output="$(_rebuild_upgrade_run REBUILD_UPGRADE_SANDBOX_CMD openshell sandbox exec -n "${sandbox}" -- bash -lc "grep -R 'platforms.discord\|DISCORD' ~/.hermes . 2>/dev/null" || true)"
if [[ "${output}" == *"discord"* || "${output}" == *"DISCORD"* ]]; then
e2e_pass "suite.rebuild.hermes_config_preserved"
else
Expand Down
8 changes: 4 additions & 4 deletions test/e2e/validation_suites/lib/sandbox_lifecycle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ sandbox_lifecycle_assert_logs_available() {

sandbox_lifecycle_assert_openshell_exec_ok() {
local id="validation.sandbox_operations.openshell_exec_ok"
sandbox_lifecycle_run_with_timeout 20 openshell sandbox exec "${E2E_SANDBOX_NAME}" -- sh -lc 'echo lifecycle-ok' >/dev/null || {
sandbox_lifecycle_run_with_timeout 20 openshell sandbox exec -n "${E2E_SANDBOX_NAME}" -- sh -lc 'echo lifecycle-ok' >/dev/null || {
sandbox_lifecycle_fail "${id}" "openshell exec failed"
return 1
}
Expand Down Expand Up @@ -139,7 +139,7 @@ sandbox_lifecycle_assert_gateway_recovers_after_probe() {
}

sandbox_lifecycle_assert_snapshot_create_list_restore_marker() {
sandbox_lifecycle_run_with_timeout 30 openshell sandbox exec "${E2E_SANDBOX_NAME}" -- sh -lc 'echo lifecycle-marker-before-snapshot > /tmp/nemoclaw-lifecycle-marker' >/dev/null || {
sandbox_lifecycle_run_with_timeout 30 openshell sandbox exec -n "${E2E_SANDBOX_NAME}" -- sh -lc 'echo lifecycle-marker-before-snapshot > /tmp/nemoclaw-lifecycle-marker' >/dev/null || {
sandbox_lifecycle_fail validation.sandbox_snapshot.marker_written "failed to write marker"
return 1
}
Expand All @@ -149,7 +149,7 @@ sandbox_lifecycle_assert_snapshot_create_list_restore_marker() {
return 1
}
sandbox_lifecycle_pass validation.sandbox_snapshot.create_succeeds "snapshot create succeeded"
sandbox_lifecycle_run_with_timeout 30 openshell sandbox exec "${E2E_SANDBOX_NAME}" -- sh -lc 'echo lifecycle-marker-after-snapshot > /tmp/nemoclaw-lifecycle-marker' >/dev/null || {
sandbox_lifecycle_run_with_timeout 30 openshell sandbox exec -n "${E2E_SANDBOX_NAME}" -- sh -lc 'echo lifecycle-marker-after-snapshot > /tmp/nemoclaw-lifecycle-marker' >/dev/null || {
sandbox_lifecycle_fail validation.sandbox_snapshot.restore_rolls_back_marker "failed to mutate marker"
return 1
}
Expand All @@ -162,7 +162,7 @@ sandbox_lifecycle_assert_snapshot_create_list_restore_marker() {
sandbox_lifecycle_fail validation.sandbox_snapshot.restore_rolls_back_marker "snapshot restore failed"
return 1
}
sandbox_lifecycle_run_with_timeout 30 openshell sandbox exec "${E2E_SANDBOX_NAME}" -- sh -lc 'test -f /tmp/nemoclaw-lifecycle-marker && grep -Fxq lifecycle-marker-before-snapshot /tmp/nemoclaw-lifecycle-marker' >/dev/null || {
sandbox_lifecycle_run_with_timeout 30 openshell sandbox exec -n "${E2E_SANDBOX_NAME}" -- sh -lc 'test -f /tmp/nemoclaw-lifecycle-marker && grep -Fxq lifecycle-marker-before-snapshot /tmp/nemoclaw-lifecycle-marker' >/dev/null || {
sandbox_lifecycle_fail validation.sandbox_snapshot.restore_rolls_back_marker "marker did not roll back"
return 1
}
Expand Down
77 changes: 77 additions & 0 deletions test/share-command-deps-probe-argv.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { createRequire } from "node:module";
import { afterEach, describe, expect, it } from "vitest";

const require = createRequire(import.meta.url);
const requireCache: Record<string, unknown> = require.cache as any;

// Regression: `nemoclaw share mount` was passing the sandbox name as a bare
// positional to `openshell sandbox exec`, so OpenShell treated it as the
// command to run and the probe always returned a non-zero exit code even
// when `/sandbox` existed. The convention in this repo is to select the
// target sandbox with `-n` (or `--name`).
// See #3889 and #3954.
describe("buildShareCommandDeps().checkSandboxPathExists probe argv", () => {
afterEach(() => {
const openshellRuntimePath = require.resolve("../dist/lib/adapters/openshell/runtime");
const shareDepsPath = require.resolve("../dist/lib/share-command-deps");
delete require.cache[openshellRuntimePath];
delete require.cache[shareDepsPath];
});

it("targets the sandbox with `-n <name>` so it is not parsed as the command", () => {
const openshellRuntimePath = require.resolve("../dist/lib/adapters/openshell/runtime");
const shareDepsPath = require.resolve("../dist/lib/share-command-deps");

let recordedArgs: readonly string[] | undefined;
requireCache[openshellRuntimePath] = {
id: openshellRuntimePath,
filename: openshellRuntimePath,
loaded: true,
exports: {
captureOpenshell: (args: readonly string[]) => {
recordedArgs = args;
return { status: 0, output: "" };
},
},
} as any;
delete require.cache[shareDepsPath];

const { buildShareCommandDeps } = require("../dist/lib/share-command-deps");
const deps = buildShareCommandDeps();
const exists = deps.checkSandboxPathExists("prachi-sbox", "/sandbox");

expect(exists).toBe(true);
expect(recordedArgs).toEqual([
"sandbox",
"exec",
"-n",
"prachi-sbox",
"--",
"test",
"-e",
"/sandbox",
]);
});

it("reports the path as missing when the probe exits non-zero", () => {
const openshellRuntimePath = require.resolve("../dist/lib/adapters/openshell/runtime");
const shareDepsPath = require.resolve("../dist/lib/share-command-deps");

requireCache[openshellRuntimePath] = {
id: openshellRuntimePath,
filename: openshellRuntimePath,
loaded: true,
exports: {
captureOpenshell: () => ({ status: 1, output: "" }),
},
} as any;
delete require.cache[shareDepsPath];

const { buildShareCommandDeps } = require("../dist/lib/share-command-deps");
const deps = buildShareCommandDeps();
expect(deps.checkSandboxPathExists("alpha", "/sandbox/missing")).toBe(false);
});
});
Loading