diff --git a/docs/reference/troubleshooting.mdx b/docs/reference/troubleshooting.mdx
index d27a152bb5f..d882f5821a1 100644
--- a/docs/reference/troubleshooting.mdx
+++ b/docs/reference/troubleshooting.mdx
@@ -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.
+
+
+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.
+
+
+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.
+
### Sandbox build fails during OpenClaw plugin install
diff --git a/scripts/install.sh b/scripts/install.sh
index 32b7074c10c..77b3eb118b3 100755
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -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
@@ -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
diff --git a/test/install-openshell-gateway-service.test.ts b/test/install-openshell-gateway-service.test.ts
index 95abed95a4f..f14b15912ec 100644
--- a/test/install-openshell-gateway-service.test.ts
+++ b/test/install-openshell-gateway-service.test.ts
@@ -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"),
);
@@ -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);