Research only — nothing was changed, built or installed. Registry data and image contents read on 2026-09-14, against the container currently running Node v22.23.2 / npm 10.9.8.
node:24-bookworm.Nothing in the image caps Node below 24. OpenClaw is the only package that requires the bump; everything else already allows it. Every native addon in play is N-API, which is the ABI-stable kind — so the usual "native module breaks on a Node major" failure class does not apply here. Staying on bookworm keeps Debian 12 and its Python 3.11, which is what Hermes's venv and the batteries-included Python stack are built against.
Two things to watch, neither a blocker: Hermes pins npm as well as Node and today's 24-bookworm only just clears it, and the whole CLI install section is best-effort || echo "… failed" — which is exactly how OpenClaw has been missing all along. A green build after the bump proves nothing; the build log has to be read.
Raw values from npm view <pkg>@<version> engines, so you can check the reading.
| package | installed | engines.node | vs Node 24 |
|---|---|---|---|
| openclaw | absent (2026.9.4 latest) | ">=24.16.0 <25 || >=26.1.0" | requires bump |
| @anthropic-ai/claude-code | 2.1.270 | ">=22.0.0" | allows |
| @google/gemini-cli | 0.59.0 | ">=20" | allows |
| @openai/codex | 0.154.0 / 0.152.1 | ">=16" | allows |
| opencode-ai | 1.18.30 | field absent | unconstrained |
| ccusage | — | field absent | unconstrained |
| playwright (chromium step) | ^1.62.1 | ">=20" | allows |
Nothing caps Node below 24. The claude-code and codex values are also less load-bearing than they look: both now ship platform binaries (@anthropic-ai/claude-code-linux-x64, @openai/codex-linux-x64), so Node is a launcher shim rather than the runtime. Same for opencode-ai, which ships opencode-linux-x64. Of the npm-installed CLIs, gemini-cli is the one that genuinely runs on the system Node — and it asks only for >=20.
Both checked on the running container rather than assumed.
/usr/local/bin/fx is an ELF 64-bit, statically linked executable installed from a release tarball. No Node involvement at all, at build time or run time. no risk~/.local/bin/hermes is a three-line shell shim whose last line is exec "~/.hermes/hermes-agent/venv/bin/python" "~/.hermes/hermes-agent/hermes" "$@". It runs on its own venv Python 3.11.2, not on Node. no runtime riskBut Hermes is not Node-free at install time, and this is the one new constraint the bump introduces. Its tree contains a full node_modules, and ~/.hermes/hermes-agent/package.json declares:
"engines": { "node": "^22.22.0 || ^24.11.0 || >=26.0.0",
"npm": "<11.10.0 || >=11.17.0" }
Node 24 is allowed. The npm range is the interesting half, because the base image decides it for you:
| Node | bundled npm | vs Hermes's npm range |
|---|---|---|
| v22.23.2 (today) | 10.9.8 | ok — under 11.10.0 |
v24.21.0 (24-bookworm today) | 11.19.0 | ok — over 11.17.0 |
| v24.16.0 (OpenClaw's floor) | 11.13.0 | inside the excluded window |
| v26.8.2 | 11.19.1 | ok |
Today's floating 24-bookworm clears it with two minor versions to spare. It would not have, three releases ago. If you want that to stay true without watching it, pin node:24.21-bookworm — the Dockerfile already carries a scar from a floating tag in the Playwright/chromium comment.
server/package.json declares "engines": { "node": ">=20.19" } — advisory only; there is no .npmrc with engine-strict anywhere in the repo, so engines produce warnings, not install failures. web/package.json declares none. There is no .nvmrc and no CI job pinning a Node version, so nothing else in the repo needs editing alongside the Dockerfile.
Compiled addons are what actually break across Node majors, and only if they use NAN/V8 APIs. Neither of these does.
| dependency | kind | evidence |
|---|---|---|
| node-pty ^1.0.0 → 1.1.0 | N-API, compiled in-image | depends on node-addon-api ^7.1.0; src/unix/pty.cc includes napi.h, not nan.h. Install is node scripts/prebuild.js || node-gyp rebuild, and make/g++ are in the image, so it recompiles against whatever headers ship. |
| @coder/libghostty-vt-node 0.1.0-beta.0 | N-API, prebuilt | node-addon-api ^8.3.1 + node-gyp-build ^4.8.4, with prebuilds/linux-x64/ present. N-API prebuilds are per-platform, not per-Node-version. |
| sqlite-vec (OpenClaw's optional dep) | prebuilt platform packages | sqlite-vec-linux-x64 etc. — a loadable SQLite extension, not a Node ABI addon. |
There is no better-sqlite3 and no node-gyp module using the V8 API anywhere in the dependency set.
The durable-state / trace work does not use an npm SQLite package. It uses the built-in node:sqlite, imported defensively in two places so a Node without it degrades rather than crashes:
server/src/traces.js:514 try { ({ DatabaseSync } = await import('node:sqlite')); } catch { /* older node: skip opencode */ }
server/src/share.js:362 try { ({ DatabaseSync } = await import('node:sqlite')); } catch { return null; }
The API surface used is small and read-only — new DatabaseSync(path, { readOnly: true }), .prepare(), .get(), .all(), one .exec(). That is the original core of the module and is unchanged from 22.5 onward. It runs unflagged today; nothing in entrypoint.sh or the package scripts passes --experimental-sqlite or suppresses warnings. The module remains experimental on 24, so expect the same ExperimentalWarning on stderr, and treat the opencode/hermes trace suites as the thing to re-run rather than assuming.
| line | newest | status | OpenClaw range | |
|---|---|---|---|---|
| 24 | 24.21.0 (2026-09-07) | Active LTS "Krypton" | satisfied (>=24.16.0 <25) | recommended |
| 25 | 25.9.0 (2026-03-31) | not LTS, stale | explicitly excluded | no |
| 26 | 26.8.2 (2026-09-09) | Current, not LTS | satisfied (>=26.1.0) | viable, unnecessary |
Take Node 24. It is the LTS line, it satisfies OpenClaw with five patch releases of headroom above the 24.16.0 floor, and it is the smaller jump. Node 26 would also work but buys nothing here and puts the fleet on a non-LTS line that will churn. Node 25 is excluded by OpenClaw and is effectively dead anyway — last release was March.
node:24-bookworm exists on Docker Hub (as do lts-bookworm and krypton-bookworm, which alias it). Keeping bookworm is the single biggest blast-radius reducer: the Debian base, its python3 3.11, apt package set, and glibc all stay exactly as they are. Hermes's venv and the /opt/py data stack are built on that Python — moving to a trixie-based tag would rebuild both against a different interpreter for no reason.
Both FROM lines change (Dockerfile:2 web builder, :11 runtime). The comment on :10 — "Node 22+: required by OpenClaw (22.19+)" — is stale and should be corrected while you are there.
engine-strict, so tightened engines elsewhere stay warnings.playwright@1.62.1 allows >=20, so the pinned-revision install step is fine. It is worth re-reading the Dockerfile's own comment there: the revision must match what the app pins, and PLAYWRIGHT_SKIP_BROWSER_GC=1 is what keeps the shared /opt/pw-browsers working. A bump does not change that logic, but it does re-run it.astral.sh and via uv tool against /usr/bin/python3. No Node coupling. Unaffected as long as the Debian base does not move.|| echo "… failed" pattern. gemini-cli, opencode, openclaw, ccusage, uv, hf, playwright and hermes all swallow their own failure. That is precisely why a missing OpenClaw shipped green build after green build. After the bump, the build log is the only evidence that anything installed..nvmrc, no CI Node matrix, no other node:22 reference in the repo.openclaw --version — the whole point; it should exist for the first time.claude, codex, gemini, opencode, hermes, fx, ccusage all still answer --version.install failed and for the Hermes step — a green image is not evidence.server/node_modules/node-pty/build/Release/pty.node exists, and terminal panes actually render (that also exercises the libghostty prebuild).node:sqlite path. The server suites test/reader-protocol, test/trace-window and test/trace-tail cover it; run them individually, since the runner stops at the first failure and test/crons.test.mjs #4 is known-red on main.npm --version inside the image, against Hermes's excluded window of 11.10.0–11.16.x.Stated rather than guessed, with the test for each:
npm install --omit=dev layer, and pty.node existing afterwards.install.sh fetched at build time; I did not run it. Test: the Hermes step in the build log, and hermes --version after.sqlite-vec dependency is prebuilt, so installation should succeed — first launch is a separate question.Sources: npm registry (npm view … engines), nodejs.org/dist/index.json for release lines and bundled npm, Docker Hub tag list, and direct inspection of the running container and a fresh read-only clone at a74b327. No file on the box was modified.