fix(runtime): bump minimum Node.js version to 22.12.0 (#5370)

* fix(runtime): bump minimum Node.js version to 22.12.0

Aligns the runtime guard with the declared package.json engines requirement.

The Matrix plugin (and potentially others) requires Node >= 22.12.0,
but the runtime guard previously allowed 22.0.0+. This caused confusing
errors like 'Cannot find module @vector-im/matrix-bot-sdk' when the real
issue was an unsupported Node version.

- Update MIN_NODE from 22.0.0 to 22.12.0
- Update error message to reflect the correct version
- Update tests to use 22.12.0 as the minimum valid version

Fixes #5292

* fix: update test versions to match MIN_NODE=22.12.0

---------

Co-authored-by: Markus Glucksberg <markus@glucksberg.com>
This commit is contained in:
Glucksberg
2026-02-05 17:42:52 -04:00
committed by GitHub
parent db8e9b37c6
commit 2ca78a8aed
3 changed files with 20 additions and 12 deletions

View File

@@ -30,7 +30,8 @@ describe("resolvePreferredNodePath", () => {
throw new Error("missing"); throw new Error("missing");
}); });
const execFile = vi.fn().mockResolvedValue({ stdout: "22.1.0\n", stderr: "" }); // Node 22.12.0+ is the minimum required version
const execFile = vi.fn().mockResolvedValue({ stdout: "22.12.0\n", stderr: "" });
const result = await resolvePreferredNodePath({ const result = await resolvePreferredNodePath({
env: {}, env: {},
@@ -51,7 +52,8 @@ describe("resolvePreferredNodePath", () => {
throw new Error("missing"); throw new Error("missing");
}); });
const execFile = vi.fn().mockResolvedValue({ stdout: "18.19.0\n", stderr: "" }); // Node 22.11.x is below minimum 22.12.0
const execFile = vi.fn().mockResolvedValue({ stdout: "22.11.0\n", stderr: "" });
const result = await resolvePreferredNodePath({ const result = await resolvePreferredNodePath({
env: {}, env: {},
@@ -92,7 +94,8 @@ describe("resolveSystemNodeInfo", () => {
throw new Error("missing"); throw new Error("missing");
}); });
const execFile = vi.fn().mockResolvedValue({ stdout: "22.0.0\n", stderr: "" }); // Node 22.12.0+ is the minimum required version
const execFile = vi.fn().mockResolvedValue({ stdout: "22.12.0\n", stderr: "" });
const result = await resolveSystemNodeInfo({ const result = await resolveSystemNodeInfo({
env: {}, env: {},
@@ -102,7 +105,7 @@ describe("resolveSystemNodeInfo", () => {
expect(result).toEqual({ expect(result).toEqual({
path: darwinNode, path: darwinNode,
version: "22.0.0", version: "22.12.0",
supported: true, supported: true,
}); });
}); });

View File

@@ -16,13 +16,16 @@ describe("runtime-guard", () => {
}); });
it("compares versions correctly", () => { it("compares versions correctly", () => {
expect(isAtLeast({ major: 22, minor: 0, patch: 0 }, { major: 22, minor: 0, patch: 0 })).toBe( expect(isAtLeast({ major: 22, minor: 12, patch: 0 }, { major: 22, minor: 12, patch: 0 })).toBe(
true, true,
); );
expect(isAtLeast({ major: 22, minor: 1, patch: 0 }, { major: 22, minor: 0, patch: 0 })).toBe( expect(isAtLeast({ major: 22, minor: 13, patch: 0 }, { major: 22, minor: 12, patch: 0 })).toBe(
true, true,
); );
expect(isAtLeast({ major: 21, minor: 9, patch: 0 }, { major: 22, minor: 0, patch: 0 })).toBe( expect(isAtLeast({ major: 22, minor: 11, patch: 0 }, { major: 22, minor: 12, patch: 0 })).toBe(
false,
);
expect(isAtLeast({ major: 21, minor: 9, patch: 0 }, { major: 22, minor: 12, patch: 0 })).toBe(
false, false,
); );
}); });
@@ -30,11 +33,12 @@ describe("runtime-guard", () => {
it("validates runtime thresholds", () => { it("validates runtime thresholds", () => {
const nodeOk: RuntimeDetails = { const nodeOk: RuntimeDetails = {
kind: "node", kind: "node",
version: "22.0.0", version: "22.12.0",
execPath: "/usr/bin/node", execPath: "/usr/bin/node",
pathEnv: "/usr/bin", pathEnv: "/usr/bin",
}; };
const nodeOld: RuntimeDetails = { ...nodeOk, version: "21.9.0" }; const nodeOld: RuntimeDetails = { ...nodeOk, version: "22.11.0" };
const nodeTooOld: RuntimeDetails = { ...nodeOk, version: "21.9.0" };
const unknown: RuntimeDetails = { const unknown: RuntimeDetails = {
kind: "unknown", kind: "unknown",
version: null, version: null,
@@ -43,6 +47,7 @@ describe("runtime-guard", () => {
}; };
expect(runtimeSatisfies(nodeOk)).toBe(true); expect(runtimeSatisfies(nodeOk)).toBe(true);
expect(runtimeSatisfies(nodeOld)).toBe(false); expect(runtimeSatisfies(nodeOld)).toBe(false);
expect(runtimeSatisfies(nodeTooOld)).toBe(false);
expect(runtimeSatisfies(unknown)).toBe(false); expect(runtimeSatisfies(unknown)).toBe(false);
}); });
@@ -73,7 +78,7 @@ describe("runtime-guard", () => {
const details: RuntimeDetails = { const details: RuntimeDetails = {
...detectRuntime(), ...detectRuntime(),
kind: "node", kind: "node",
version: "22.0.0", version: "22.12.0",
execPath: "/usr/bin/node", execPath: "/usr/bin/node",
}; };
expect(() => assertSupportedRuntime(runtime, details)).not.toThrow(); expect(() => assertSupportedRuntime(runtime, details)).not.toThrow();

View File

@@ -9,7 +9,7 @@ type Semver = {
patch: number; patch: number;
}; };
const MIN_NODE: Semver = { major: 22, minor: 0, patch: 0 }; const MIN_NODE: Semver = { major: 22, minor: 12, patch: 0 };
export type RuntimeDetails = { export type RuntimeDetails = {
kind: RuntimeKind; kind: RuntimeKind;
@@ -88,7 +88,7 @@ export function assertSupportedRuntime(
runtime.error( runtime.error(
[ [
"openclaw requires Node >=22.0.0.", "openclaw requires Node >=22.12.0.",
`Detected: ${runtimeLabel} (exec: ${execLabel}).`, `Detected: ${runtimeLabel} (exec: ${execLabel}).`,
`PATH searched: ${details.pathEnv}`, `PATH searched: ${details.pathEnv}`,
"Install Node: https://nodejs.org/en/download", "Install Node: https://nodejs.org/en/download",