Skip to content
Merged
2 changes: 1 addition & 1 deletion .agents/skills/nemoclaw-get-started/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc
| Dependency | Version |
|------------|----------------------------------|
| Linux | Ubuntu 22.04 LTS or later |
| Node.js | 20 or later |
| Node.js | 22.16 or later |
| npm | 10 or later |
| Container runtime | Supported runtime installed and running |
| [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ If you see an unsupported platform error, verify that you are running on a suppo

### Node.js version is too old

NemoClaw requires Node.js 20 or later.
NemoClaw requires Node.js 22.16 or later.
If the installer exits with a Node.js version error, check your current version:

```console
$ node --version
```

If the version is below 20, install a supported release.
If the version is below 22.16, install a supported release.
If you use nvm, run:

```console
$ nvm install 20
$ nvm use 20
$ nvm install 22
$ nvm use 22
```

Then re-run the installer.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Open an issue when you encounter one of the following situations.

Install the following before you begin.

- Node.js 20+ and npm 10+
- Node.js 22.16+ and npm 10+
- Python 3.11+ (for blueprint and documentation builds)
- Docker (running)
- [uv](https://docs.astral.sh/uv/) (for Python dependency management)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc
| Dependency | Version |
|------------|----------------------------------|
| Linux | Ubuntu 22.04 LTS or later |
| Node.js | 20 or later |
| Node.js | 22.16 or later |
| npm | 10 or later |
| Container runtime | Supported runtime installed and running |
| [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed |
Expand Down
8 changes: 4 additions & 4 deletions docs/reference/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,19 @@ If you see an unsupported platform error, verify that you are running on a suppo

### Node.js version is too old

NemoClaw requires Node.js 20 or later.
NemoClaw requires Node.js 22.16 or later.
If the installer exits with a Node.js version error, check your current version:

```console
$ node --version
```

If the version is below 20, install a supported release.
If the version is below 22.16, install a supported release.
If you use nvm, run:

```console
$ nvm install 20
$ nvm use 20
$ nvm install 22
$ nvm use 22
```

Then re-run the installer.
Expand Down
14 changes: 8 additions & 6 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -213,15 +213,17 @@ spin() {

command_exists() { command -v "$1" &>/dev/null; }

MIN_NODE_MAJOR=20
MIN_NODE_VERSION="22.16.0"
MIN_NPM_MAJOR=10
RECOMMENDED_NODE_MAJOR=22
RUNTIME_REQUIREMENT_MSG="NemoClaw requires Node.js >=${MIN_NODE_MAJOR} and npm >=${MIN_NPM_MAJOR} (recommended Node.js ${RECOMMENDED_NODE_MAJOR})."
RUNTIME_REQUIREMENT_MSG="NemoClaw requires Node.js >=${MIN_NODE_VERSION} and npm >=${MIN_NPM_MAJOR}."
NEMOCLAW_SHIM_DIR="${HOME}/.local/bin"
ORIGINAL_PATH="${PATH:-}"

# Compare two semver strings (major.minor.patch). Returns 0 if $1 >= $2.
# Rejects prerelease suffixes (e.g. "22.16.0-rc.1") to avoid arithmetic errors.
version_gte() {
[[ "$1" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || return 1
[[ "$2" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || return 1
local -a a b
IFS=. read -ra a <<<"$1"
IFS=. read -ra b <<<"$2"
Expand Down Expand Up @@ -300,7 +302,7 @@ ensure_supported_runtime() {
[[ "$node_major" =~ ^[0-9]+$ ]] || error "Could not determine Node.js version from '${node_version}'. ${RUNTIME_REQUIREMENT_MSG}"
[[ "$npm_major" =~ ^[0-9]+$ ]] || error "Could not determine npm version from '${npm_version}'. ${RUNTIME_REQUIREMENT_MSG}"

if ((node_major < MIN_NODE_MAJOR || npm_major < MIN_NPM_MAJOR)); then
if ! version_gte "${node_version#v}" "$MIN_NODE_VERSION" || ((npm_major < MIN_NPM_MAJOR)); then
error "Unsupported runtime detected: Node.js ${node_version:-unknown}, npm ${npm_version:-unknown}. ${RUNTIME_REQUIREMENT_MSG} Upgrade Node.js and rerun the installer."
fi

Expand Down Expand Up @@ -344,9 +346,9 @@ install_nodejs() {
spin "Installing nvm..." bash "$nvm_tmp"
rm -f "$nvm_tmp"
ensure_nvm_loaded
spin "Installing Node.js ${RECOMMENDED_NODE_MAJOR}..." bash -c ". \"$NVM_DIR/nvm.sh\" && nvm install ${RECOMMENDED_NODE_MAJOR} --no-progress"
spin "Installing Node.js 22..." bash -c ". \"$NVM_DIR/nvm.sh\" && nvm install 22 --no-progress"
ensure_nvm_loaded
nvm use "${RECOMMENDED_NODE_MAJOR}" --silent
nvm use 22 --silent
info "Node.js installed: $(node --version)"
}

Expand Down
10 changes: 5 additions & 5 deletions nemoclaw/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions nemoclaw/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"yaml": "^2.4.0"
},
"devDependencies": {
"@types/node": "^20.19.37",
"@types/node": "^22.0.0",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"eslint": "^9.39.4",
Expand All @@ -39,7 +39,7 @@
"vitest": "^4.1.0"
},
"engines": {
"node": ">=22.0.0"
"node": ">=22.16.0"
},
"files": [
"dist/",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
".dockerignore"
],
"engines": {
"node": ">=22.0.0"
"node": ">=22.16.0"
},
"repository": {
"type": "git",
Expand Down
33 changes: 24 additions & 9 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,9 @@ refresh_path() {
fi
}

MIN_NODE_MAJOR=20
MIN_NODE_VERSION="22.16.0"
MIN_NPM_MAJOR=10
RECOMMENDED_NODE_MAJOR=22
RUNTIME_REQUIREMENT_MSG="NemoClaw requires Node.js >=${MIN_NODE_MAJOR} and npm >=${MIN_NPM_MAJOR} (recommended Node.js ${RECOMMENDED_NODE_MAJOR})."
RUNTIME_REQUIREMENT_MSG="NemoClaw requires Node.js >=${MIN_NODE_VERSION} and npm >=${MIN_NPM_MAJOR}."
Comment thread
coderabbitai[bot] marked this conversation as resolved.

OS="$(uname -s)"
ARCH="$(uname -m)"
Expand Down Expand Up @@ -170,6 +169,22 @@ fi

info "Node.js manager: $NODE_MGR"

# Compare two semver strings (major.minor.patch). Returns 0 if $1 >= $2.
# Rejects prerelease suffixes (e.g. "22.16.0-rc.1") to avoid arithmetic errors.
version_gte() {
[[ "$1" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || return 1
[[ "$2" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || return 1
local -a a b
IFS=. read -ra a <<<"$1"
IFS=. read -ra b <<<"$2"
for i in 0 1 2; do
local ai=${a[$i]:-0} bi=${b[$i]:-0}
if ((ai > bi)); then return 0; fi
if ((ai < bi)); then return 1; fi
done
return 0
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

version_major() {
printf '%s\n' "${1#v}" | cut -d. -f1
}
Expand All @@ -187,7 +202,7 @@ ensure_supported_runtime() {
[[ "$node_major" =~ ^[0-9]+$ ]] || fail "Could not determine Node.js version from '${node_version}'. ${RUNTIME_REQUIREMENT_MSG}"
[[ "$npm_major" =~ ^[0-9]+$ ]] || fail "Could not determine npm version from '${npm_version}'. ${RUNTIME_REQUIREMENT_MSG}"

if ((node_major < MIN_NODE_MAJOR || npm_major < MIN_NPM_MAJOR)); then
if ! version_gte "${node_version#v}" "$MIN_NODE_VERSION" || ((npm_major < MIN_NPM_MAJOR)); then
fail "Unsupported runtime detected: Node.js ${node_version:-unknown}, npm ${npm_version:-unknown}. ${RUNTIME_REQUIREMENT_MSG} Upgrade Node.js and rerun the installer."
fi

Expand All @@ -197,13 +212,13 @@ ensure_supported_runtime() {
# ── Install Node.js 22 if needed ────────────────────────────────

install_node() {
local current_major=""
local current_version=""
if command -v node >/dev/null 2>&1; then
current_major="$(node -v 2>/dev/null | sed 's/^v//' | cut -d. -f1)"
current_version="$(node -v 2>/dev/null | sed 's/^v//')"
fi

if [ "$current_major" = "22" ]; then
info "Node.js 22 already installed: $(node -v)"
if [ -n "$current_version" ] && version_gte "$current_version" "$MIN_NODE_VERSION"; then
info "Node.js v${current_version} meets minimum requirement (>= v${MIN_NODE_VERSION})"
return 0
fi

Expand Down Expand Up @@ -239,7 +254,7 @@ install_node() {
sudo apt-get install -y -qq nodejs >/dev/null 2>&1
;;
none)
fail "No Node.js version manager found. Install Node.js 22 manually, then re-run."
fail "No Node.js version manager found. Install Node.js >=${MIN_NODE_VERSION} manually, then re-run."
;;
esac

Expand Down
48 changes: 37 additions & 11 deletions test/install-preflight.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ function writeExecutable(target, contents) {
// Helpers shared across suites
// ---------------------------------------------------------------------------

/** Fake node that reports v22.14.0. */
/** Fake node that reports v22.16.0. */
function writeNodeStub(fakeBin) {
writeExecutable(
path.join(fakeBin, "node"),
`#!/usr/bin/env bash
if [ "$1" = "--version" ] || [ "$1" = "-v" ]; then echo "v22.14.0"; exit 0; fi
if [ "$1" = "--version" ] || [ "$1" = "-v" ]; then echo "v22.16.0"; exit 0; fi
if [ "$1" = "-e" ]; then
if [[ "$2" == *"dependencies.openclaw"* ]]; then
echo "2026.3.11"
Expand Down Expand Up @@ -100,7 +100,7 @@ exit 98
const output = `${result.stdout}${result.stderr}`;
expect(result.status).not.toBe(0);
expect(output).toMatch(/Unsupported runtime detected/);
expect(output).toMatch(/Node\.js >=20 and npm >=10/);
expect(output).toMatch(/Node\.js >=22\.16\.0 and npm >=10/);
expect(output).toMatch(/v18\.19\.1/);
expect(output).toMatch(/9\.8\.1/);
});
Expand All @@ -117,7 +117,7 @@ exit 98
path.join(fakeBin, "node"),
`#!/usr/bin/env bash
if [ "$1" = "--version" ]; then
echo "v22.14.0"
echo "v22.16.0"
exit 0
fi
if [ "$1" = "-e" ]; then
Expand Down Expand Up @@ -212,7 +212,7 @@ exit 98
path.join(fakeBin, "node"),
`#!/usr/bin/env bash
if [ "$1" = "--version" ]; then
echo "v22.14.0"
echo "v22.16.0"
exit 0
fi
if [ "$1" = "-e" ]; then
Expand Down Expand Up @@ -295,7 +295,7 @@ exit 98
path.join(fakeBin, "node"),
`#!/usr/bin/env bash
if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then
echo "v22.14.0"
echo "v22.16.0"
exit 0
fi
exit 99
Expand Down Expand Up @@ -375,7 +375,7 @@ echo "Darwin"
path.join(fakeBin, "node"),
`#!/usr/bin/env bash
if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then
echo "v22.14.0"
echo "v22.16.0"
exit 0
fi
if [ "$1" = "-e" ]; then
Expand Down Expand Up @@ -641,7 +641,7 @@ fi`,
path.join(fakeBin, "node"),
`#!/usr/bin/env bash
if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then
echo "v22.14.0"
echo "v22.16.0"
exit 0
fi
if [ "$1" = "-e" ]; then
Expand Down Expand Up @@ -1277,15 +1277,15 @@ exit 0`,
expect(`${result.stdout}${result.stderr}`).toMatch(/npm was not found on PATH/);
});

it("succeeds with acceptable Node.js 20 and npm 10", () => {
it("succeeds with acceptable Node.js 22.16 and npm 10", () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-runtime-ok-"));
const fakeBin = path.join(tmp, "bin");
fs.mkdirSync(fakeBin);

writeExecutable(
path.join(fakeBin, "node"),
`#!/usr/bin/env bash
if [ "$1" = "--version" ]; then echo "v20.0.0"; exit 0; fi
if [ "$1" = "--version" ]; then echo "v22.16.0"; exit 0; fi
exit 0`,
);
writeExecutable(
Expand All @@ -1301,6 +1301,32 @@ exit 0`,
expect(`${result.stdout}${result.stderr}`).toMatch(/Runtime OK/);
});

it("rejects Node.js 20 which is below the 22.16 minimum", () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-runtime-node20-"));
const fakeBin = path.join(tmp, "bin");
fs.mkdirSync(fakeBin);

writeExecutable(
path.join(fakeBin, "node"),
`#!/usr/bin/env bash
if [ "$1" = "--version" ]; then echo "v20.18.0"; exit 0; fi
exit 0`,
);
writeExecutable(
path.join(fakeBin, "npm"),
`#!/usr/bin/env bash
if [ "$1" = "--version" ]; then echo "10.9.2"; exit 0; fi
exit 0`,
);

const result = callEnsureSupportedRuntime(fakeBin);

expect(result.status).not.toBe(0);
const output = `${result.stdout}${result.stderr}`;
expect(output).toMatch(/Unsupported runtime detected/);
expect(output).toMatch(/v20\.18\.0/);
});

it("rejects node that returns a non-numeric version", () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-runtime-badver-"));
const fakeBin = path.join(tmp, "bin");
Expand Down Expand Up @@ -1346,7 +1372,7 @@ describe("curl-pipe installer release-tag resolution", () => {
writeExecutable(
path.join(fakeBin, "node"),
`#!/usr/bin/env bash
if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then echo "v22.14.0"; exit 0; fi
if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then echo "v22.16.0"; exit 0; fi
if [ "$1" = "-e" ]; then exit 1; fi
exit 99`,
);
Expand Down
Loading