Skip to content
21 changes: 21 additions & 0 deletions docs/reference/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,27 @@ Upgrade NemoClaw to a version that supports your OpenShell release, or install a
For fresh installs, NemoClaw passes the blueprint range to `install-openshell.sh` and resolves a compatible published OpenShell release before downloading.
If GitHub release metadata is unavailable, the script uses its bundled fallback pin and the post-install gate still enforces the configured range.

### Installer Reports an OpenShell Gateway Version Mismatch

On Linux, an existing OpenShell package can provide a systemd user service that starts a different gateway version from the user-local version that NemoClaw installs.
The installer stops before onboarding instead of using the two versions together.
The error reports both gateway versions and binary paths.

<Warning>
Do not remove the existing OpenShell package if its gateway manages resources outside NemoClaw.
Package removal can stop that gateway.
Align the package with the version in the installer error, or plan the migration of those resources first.
</Warning>

If you no longer need the APT-installed OpenShell package, remove it and rerun the installer:

```bash
sudo apt remove openshell
curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash
```

The next installer run must continue past the OpenShell installation step without reporting a version mismatch.

<AgentOnly variant="openclaw">

### Sandbox build fails during OpenClaw plugin install
Expand Down
45 changes: 45 additions & 0 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,50 @@ upstream_openshell_gateway_user_service_installed() {
|| [[ -f /lib/systemd/user/openshell-gateway.service ]]
}

resolve_upstream_openshell_gateway_bin_for_service() {
local exec_start gateway_bin
local -a gateway_bins=()
exec_start="$(systemctl --user show openshell-gateway.service --property=ExecStart --value 2>/dev/null)" \
|| return 1
while IFS= read -r gateway_bin; do
gateway_bins+=("$gateway_bin")
done < <(
printf '%s\n' "$exec_start" \
| grep -oE 'path=[^ ;}]+' \
| sed 's/^path=//' \
| sort -u
)
[[ "${#gateway_bins[@]}" -eq 1 ]] || return 1
gateway_bin="${gateway_bins[0]}"
[[ "$gateway_bin" == /*/openshell-gateway && -x "$gateway_bin" ]] || return 1
printf '%s\n' "$gateway_bin"
}

openshell_binary_version() {
local binary="${1:-}" version_output
[[ -x "$binary" ]] || return 1
version_output="$("$binary" --version 2>/dev/null)" || return 1
printf '%s\n' "$version_output" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1
}

require_compatible_upstream_openshell_gateway_service() {
local nemoclaw_gateway_bin upstream_gateway_bin nemoclaw_version upstream_version
nemoclaw_gateway_bin="$(resolve_openshell_gateway_bin_for_service)" \
|| error "Could not locate the NemoClaw OpenShell gateway binary before checking the existing upstream service."
if ! trusted_openshell_gateway_bin_for_service "$nemoclaw_gateway_bin"; then
error "OpenShell gateway user service binary path is not a trusted install path: $nemoclaw_gateway_bin"
fi
upstream_gateway_bin="$(resolve_upstream_openshell_gateway_bin_for_service)" \
|| error "Could not locate the gateway binary used by the existing upstream OpenShell user service. Remove or repair that OpenShell installation, then rerun the installer."
nemoclaw_version="$(openshell_binary_version "$nemoclaw_gateway_bin")" \
|| error "Could not determine the NemoClaw OpenShell gateway version at $nemoclaw_gateway_bin."
upstream_version="$(openshell_binary_version "$upstream_gateway_bin")" \
|| error "Could not determine the existing upstream OpenShell gateway version at $upstream_gateway_bin."
if [[ "$nemoclaw_version" != "$upstream_version" ]]; then
error "OpenShell gateway version mismatch: NemoClaw installed ${nemoclaw_version} at ${nemoclaw_gateway_bin}, but the existing upstream user service uses ${upstream_version} at ${upstream_gateway_bin}. Align or remove the upstream OpenShell package (for apt installs: sudo apt remove openshell), then rerun the installer."
fi
}

macos_openshell_homebrew_gateway_service_installed() {
[[ "$(uname -s)" == "Darwin" ]] || return 1
command -v brew >/dev/null 2>&1 || return 1
Expand Down Expand Up @@ -1386,6 +1430,7 @@ install_nemoclaw_openshell_gateway_user_service() {
if [[ -f "$service_path" ]] && ! is_nemoclaw_openshell_gateway_user_service "$service_path"; then
error "Refusing to replace non-NemoClaw OpenShell gateway user service: $service_path"
fi
require_compatible_upstream_openshell_gateway_service
info "OpenShell upstream gateway user service is staged; onboarding will select and start it."
return 0
fi
Expand Down
74 changes: 73 additions & 1 deletion test/install-openshell-gateway-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,16 +139,30 @@ describe("install.sh OpenShell gateway service", () => {
expect(fs.existsSync(servicePath(home))).toBe(false);
});

it("defers a marked unit when an upstream service exists (#6903)", () => {
it("defers a marked unit when the upstream service version matches (#6903)", () => {
const home = makeTempRoot();
const unitPath = servicePath(home);
const nemoclawGatewayBin = userGatewayBin(home);
const upstreamGatewayBin = path.join(home, "usr", "bin", "openshell-gateway");
fs.mkdirSync(path.dirname(unitPath), { recursive: true });
fs.writeFileSync(unitPath, "# NEMOCLAW_MANAGED_OPENSHELL_GATEWAY=1\n");
fs.mkdirSync(path.dirname(upstreamGatewayBin), { recursive: true });
writeExecutable(
nemoclawGatewayBin,
"#!/usr/bin/env bash\nprintf 'openshell-gateway 0.0.85\\n'\n",
);
writeExecutable(
upstreamGatewayBin,
"#!/usr/bin/env bash\nprintf 'openshell-gateway 0.0.85\\n'\n",
);

const result = runInstallHelper(
home,
[
"upstream_openshell_gateway_user_service_installed() { return 0; }",
`resolve_openshell_gateway_bin_for_service() { printf '%s\\n' ${JSON.stringify(nemoclawGatewayBin)}; }`,
`resolve_upstream_openshell_gateway_bin_for_service() { printf '%s\\n' ${JSON.stringify(upstreamGatewayBin)}; }`,
"trusted_openshell_gateway_bin_for_service() { return 0; }",
"install_nemoclaw_openshell_gateway_user_service",
].join("\n"),
);
Expand All @@ -158,6 +172,64 @@ describe("install.sh OpenShell gateway service", () => {
expect(result.stdout).toContain("upstream gateway user service is staged");
});

it("stops before onboarding when the upstream service version differs (#8051)", () => {
const home = makeTempRoot();
const nemoclawGatewayBin = userGatewayBin(home);
const upstreamGatewayBin = path.join(home, "usr", "bin", "openshell-gateway");
fs.mkdirSync(path.dirname(upstreamGatewayBin), { recursive: true });
writeExecutable(
nemoclawGatewayBin,
"#!/usr/bin/env bash\nprintf 'openshell-gateway 0.0.85\\n'\n",
);
writeExecutable(
upstreamGatewayBin,
"#!/usr/bin/env bash\nprintf 'openshell-gateway 0.0.91\\n'\n",
);

const result = runInstallHelper(
home,
[
"upstream_openshell_gateway_user_service_installed() { return 0; }",
`resolve_openshell_gateway_bin_for_service() { printf '%s\\n' ${JSON.stringify(nemoclawGatewayBin)}; }`,
`resolve_upstream_openshell_gateway_bin_for_service() { printf '%s\\n' ${JSON.stringify(upstreamGatewayBin)}; }`,
"trusted_openshell_gateway_bin_for_service() { return 0; }",
"install_nemoclaw_openshell_gateway_user_service",
].join("\n"),
);

expect(result.status).toBe(1);
expect(result.stderr).toContain("OpenShell gateway version mismatch");
expect(result.stderr).toContain("0.0.85");
expect(result.stderr).toContain("0.0.91");
expect(result.stderr).toContain("sudo apt remove openshell");
expect(result.stdout).not.toContain("upstream gateway user service is staged");
expect(fs.existsSync(servicePath(home))).toBe(false);
});

it("resolves the gateway from the effective upstream ExecStart (#8051)", () => {
const home = makeTempRoot();
const conventionalGatewayBin = path.join(home, "usr", "bin", "openshell-gateway");
const overriddenGatewayBin = path.join(home, "opt", "openshell", "openshell-gateway");
const systemctlBin = path.join(home, "systemctl-bin");
fs.mkdirSync(path.dirname(conventionalGatewayBin), { recursive: true });
fs.mkdirSync(path.dirname(overriddenGatewayBin), { recursive: true });
fs.mkdirSync(systemctlBin);
writeExecutable(conventionalGatewayBin, "#!/usr/bin/env bash\nexit 0\n");
writeExecutable(overriddenGatewayBin, "#!/usr/bin/env bash\nexit 0\n");
writeExecutable(
path.join(systemctlBin, "systemctl"),
`#!/usr/bin/env bash\nprintf '{ path=${overriddenGatewayBin} ; argv[]=${overriddenGatewayBin} ; ignore_errors=no ; }\\n'\n`,
);

const result = runInstallHelper(home, "resolve_upstream_openshell_gateway_bin_for_service", {
PATH: `${systemctlBin}:${path.dirname(process.execPath)}:${TEST_SYSTEM_PATH}`,
});

expect(result.status).toBe(0);
expect(result.stdout.trim()).toBe(overriddenGatewayBin);
expect(result.stdout).not.toContain(conventionalGatewayBin);
});

it("does not overwrite a foreign unit at the NemoClaw path (#6903)", () => {
const home = makeTempRoot();
const unitPath = servicePath(home);
Expand Down
Loading