From 0b39dfec098421e7d0b4be65b9376c87ebde6bdc Mon Sep 17 00:00:00 2001 From: JATMN Date: Fri, 23 Oct 2026 16:01:37 -0700 Subject: [PATCH] chore: require Node 24.22.1+ and default to Node 26 (#2365) Co-authored-by: jatmn <12479882+jatmn@users.noreply.github.com> --- .github/workflows/install-hygiene.yml | 2 +- .github/workflows/pr-checks.yml | 14 +++---- .github/workflows/release.yml | 10 ++--- .node-version | 2 +- .nvmrc | 2 +- AGENTS.md | 2 +- ANDROID_INSTALL.md | 2 +- CONTRIBUTING.md | 4 +- Dockerfile | 4 +- README.md | 9 ++--- bun.lock | 8 ++-- docs/advanced-setup.md | 8 ++-- docs/non-technical-setup.md | 2 +- docs/quick-start-mac-linux.md | 2 +- docs/quick-start-windows.md | 2 +- package.json | 4 +- scripts/benchmark-openclaude-startup.mjs | 2 +- scripts/externalsValidation.test.ts | 2 +- scripts/externalsValidation.ts | 2 +- scripts/system-check.test.ts | 33 ++++++++++++---- src/bootstrap/state.ts | 2 +- src/entrypoints/cli.tsx | 2 +- src/utils/interruptionTrace.ts | 7 +--- src/utils/nodeRuntime.test.ts | 49 +++++++++++++++++------- src/utils/nodeRuntime.ts | 10 +++-- src/utils/readFileInRange.ts | 3 +- src/utils/withResolvers.ts | 2 +- web/public/llms.txt | 2 +- web/src/pages/docs/installation.astro | 2 +- web/src/pages/index.astro | 2 +- 30 files changed, 119 insertions(+), 78 deletions(-) diff --git a/.github/workflows/install-hygiene.yml b/.github/workflows/install-hygiene.yml index 3d9018502..9d06877e2 100644 --- a/.github/workflows/install-hygiene.yml +++ b/.github/workflows/install-hygiene.yml @@ -28,7 +28,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - node-version: [22, 24] + node-version: ["24.22.1", "26"] steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index cef088056..e3d29b8c7 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -48,7 +48,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: "22" + node-version-file: .node-version - name: Set up Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 @@ -82,7 +82,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: "22" + node-version-file: .node-version - name: Set up Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 @@ -123,7 +123,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: "22" + node-version-file: .node-version - name: Set up Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 @@ -160,7 +160,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: "22" + node-version-file: .node-version - name: Set up Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 @@ -191,7 +191,7 @@ jobs: timeout-minutes: 5 strategy: matrix: - node-version: ["22", "24.11.x"] + node-version: ["24.22.1", "26"] steps: - name: Check out repository @@ -243,7 +243,7 @@ jobs: - name: Set up minimum supported Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: "22.0.0" + node-version: "24.22.1" - name: Set up Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 @@ -314,7 +314,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: 22 + node-version-file: .node-version - name: Set up Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 195116dff..4e92c55d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,8 +30,8 @@ jobs: release-type: node # Zero-warning install gate: pack the release tag and install it globally in - # a sandbox (cold + upgrade scenarios) on the supported Node floor (22 → - # npm 10) and current (24 → npm 11), whose warning output differs. Publish + # a sandbox (cold + upgrade scenarios) on the supported Node floor (24.22.1) + # and current (26), both shipping npm 11. Publish # is blocked unless the real `npm install -g` experience is clean. install-verify: name: Verify clean npm install (Node ${{ matrix.node-version }}) @@ -43,7 +43,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [22, 24] + node-version: ["24.22.1", "26"] steps: - name: Checkout release tag uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -89,7 +89,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: - node-version: 24 + node-version-file: .node-version registry-url: https://registry.npmjs.org - name: Set up Bun @@ -110,7 +110,7 @@ jobs: run: bun run smoke # Final install-hygiene gate on the publishing machine itself (the - # install-verify job covered Node 22/24 already); packs with + # install-verify job covered Node 24.22.1/26 already); packs with # --ignore-scripts to reuse the dist/ built above. Supersedes the old # `npm pack --dry-run` (contents are asserted inside the verify). - name: Verify zero-warning global install diff --git a/.node-version b/.node-version index 2bd5a0a98..6f4247a62 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22 +26 diff --git a/.nvmrc b/.nvmrc index 2bd5a0a98..6f4247a62 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22 +26 diff --git a/AGENTS.md b/AGENTS.md index 28417ffc0..3828c5c77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ This guide is for AI coding agents working in the OpenClaude repository. Read it OpenClaude is a coding-agent CLI for cloud and local model providers. It supports OpenAI-compatible APIs, Anthropic, Gemini, DeepSeek, Ollama, MCP, local backends, slash commands, tools, agents, and a React/Ink terminal UI. -The installed CLI runs on Node.js `>=22.0.0`. Bun is used for source builds, scripts, dependency management, and tests. +The installed CLI runs on Node.js `>=24.22.1` (Node 26 recommended/default). Bun is used for source builds, scripts, dependency management, and tests. ## Work Style diff --git a/ANDROID_INSTALL.md b/ANDROID_INSTALL.md index 357f19eab..f65868801 100644 --- a/ANDROID_INSTALL.md +++ b/ANDROID_INSTALL.md @@ -36,7 +36,7 @@ pkg install nodejs-lts git proot-distro Verify Node.js: ```bash -node --version # should be v20+ +node --version # requires v24.22.1 or newer; Node 26 recommended/default ``` ### Step 3 — Clone OpenClaude diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 379b255be..e8a12ce6b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -211,7 +211,7 @@ When submitting provider changes: ## Local Setup -Use Node.js `>=22.0.0` and Bun `1.4.4` (tracked in `.bun-version`). +Use Node.js `>=24.22.1` (Node 26 recommended/default) and Bun `1.4.4` (tracked in `.bun-version`). Install dependencies: @@ -320,7 +320,7 @@ Notes on the local preflight: - `bun run test` runs test files in a shared process and requires Bun's completed JUnit report as well as a successful exit. `test:full` runs all eight native Bun shards serially to release memory between groups. Use `bun run scripts/run-tests.ts --shards=N --shard-index=K` with the same test flags to run only shard K of N. `test:full` then applies the same completion check to the conversation-arc pass and the context-efficiency pass with `HISTORY_SNIP` enabled. Every shard must produce a complete report; a failed shard does not prevent the remaining shards from running. A test that terminates the runner early fails the command, even if it exits with code zero. Each shard has a ten-minute deadline (override with `--shard-timeout-ms=`), reports its start/end and elapsed time, and kills its process group on timeout or cancellation. A timeout stops the remaining shards and exits nonzero. On Linux, shard output is relayed from temporary regular files; diagnostics remain visible during execution. Temporary reports and logs are removed after the check. CI runs `check:static` in `smoke-and-tests`, runs the eight unit shards as parallel `unit-tests` jobs, and runs each dedicated feature pass once in `dedicated-tests`. Each unit shard builds the CLI before testing. CI caps these jobs at 15 minutes each and the other jobs at five minutes. - Fetching upstream `main` by URL avoids assuming that a fork checkout's `origin` points at Twigpine/openclaude. `FETCH_HEAD` is the fetched upstream tip. The scan deliberately uses local `HEAD` so it includes commits that have not been pushed yet; CI uses the pushed PR head SHA after the push. - The web CI job runs on PRs that change `web/**`, root `package.json` or `bun.lock`, `.bun-version`, or `.github/workflows/pr-checks.yml`, and on every push to `main`. Contributors do not need to run the web suite locally for changes that cannot affect the site. -- This preflight covers the same command families as CI, but it does not reproduce CI exactly in one shell. When selected, CI runs static checks with Node 22 available for smoke checks, runs the unit shards and dedicated feature passes under Node 22, separately builds and checks launcher compatibility under Node 22 and 24.11.x, separately builds and launches under exact Node 22.0.0, and executes every job on a clean runner. +- This preflight covers the same command families as CI, but it does not reproduce CI exactly in one shell. When selected, CI runs static checks with Node 26 available for smoke checks, runs the unit shards and dedicated feature passes under Node 26, separately builds and checks launcher compatibility under Node 24.22.1 and 26, separately builds and launches under exact Node 24.22.1, and executes every job on a clean runner. If a local or GitHub CI check fails, first determine whether the PR owns the failure. Treat a failure as pre-existing only when you can reproduce it on the PR's current base (or link to the same failure on a current `main` CI run) and the PR neither changes the causal surface nor claims to fix that problem. diff --git a/Dockerfile b/Dockerfile index 11c797eb4..e12ba30b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # ---- build stage ---- -FROM node:22-slim AS build +FROM node:26-slim AS build WORKDIR /app @@ -28,7 +28,7 @@ RUN bun run build RUN rm -rf node_modules && bun install --frozen-lockfile --production # ---- runtime stage ---- -FROM node:22-slim +FROM node:26-slim WORKDIR /app diff --git a/README.md b/README.md index e3dad94f3..18022b6f2 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ OpenClaude is also mirrored to Twigpine: ### Install -OpenClaude requires Node.js `>=22.0.0` for npm installs and runtime. Bun is +OpenClaude requires Node.js `>=24.22.1` for npm installs and runtime (Node 26 recommended/default). Bun is only needed for source builds and local development. ```bash @@ -489,7 +489,7 @@ and client generation from `src/proto/openclaude.proto`. ## Development -Use Node.js `>=22.0.0` and Bun `1.4.4` for source builds. +Use Node.js `>=24.22.1` (Node 26 recommended/default) and Bun `1.4.4` for source builds. ```bash bun install @@ -515,9 +515,8 @@ bun run build bun run benchmark:startup ``` -The benchmark requires Node `>=22.8.0`, where the compile-cache API was added; -the built OpenClaude launcher continues to support the declared Node `>=22.0.0` -runtime range. +The benchmark and built OpenClaude launcher require Node `>=24.22.1`; +Node 26 is recommended/default. The benchmark defaults to 30 separate-process warm runs and 10 isolated empty-cache runs. It reports the median, IQR, MAD, first cache-populating run, diff --git a/bun.lock b/bun.lock index 95fb5a32f..eac1554fd 100644 --- a/bun.lock +++ b/bun.lock @@ -27,7 +27,7 @@ "@smithy/core": "3.24.3", "@smithy/node-http-handler": "4.7.3", "@types/bun": "1.4.4", - "@types/node": "25.5.0", + "@types/node": "^24.19.2", "@types/react": "19.2.14", "@typescript-eslint/eslint-plugin": "^8.69.0", "@typescript-eslint/parser": "^8.69.0", @@ -539,7 +539,7 @@ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + "@types/node": ["@types/node@24.19.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ=="], "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], @@ -1219,7 +1219,7 @@ "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="], - "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], @@ -1415,6 +1415,8 @@ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "bun-types/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "cli-highlight/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 56781de03..cf300c132 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -4,7 +4,7 @@ This guide is for users who want source builds, Bun workflows, provider profiles ## Install Options -OpenClaude requires Node.js `>=22.0.0` for npm installs and runtime. Bun is +OpenClaude requires Node.js `>=24.22.1` for npm installs and runtime (Node 26 recommended/default). Bun is only required when building or running from source. ### Option A: npm @@ -424,7 +424,7 @@ the percentage through the OpenClaude launcher. OpenClaude converts `--max-old-space-size-percentage` and `OPENCLAUDE_NODE_MAX_OLD_SPACE_SIZE_PERCENTAGE` to an explicit `--max-old-space-size` in megabytes so the same command works on every supported -Node version, including `22.0.0`: +Node version, including `24.22.1`: ```bash openclaude --max-old-space-size-percentage=50 @@ -441,7 +441,7 @@ Heap size precedence, earlier wins: Putting the native flag in `NODE_OPTIONS` or `node --max-old-space-size-percentage=…` only works on Node versions that accept -that option; Node 22.0.0 rejects it before OpenClaude starts. When a supporting +that option. When a supporting Node already applied that native flag, OpenClaude leaves it in place and does not append `8192`. Launcher-only flags (`--max-old-space-size-percentage`, `--max-memory=`) are still stripped so Commander can start. That native-flag @@ -507,7 +507,7 @@ host. Without this variable the behavior is unchanged. | `OLLAMA_BASE_URL` | Ollama only | Optional Ollama server base URL used for model discovery and the signed-in local Web Search proxy. The Ollama provider profile normally supplies the equivalent `OPENAI_BASE_URL`. | | `OLLAMA_API_KEY` | Ollama Web Search only | Enables hosted Ollama search at `https://ollama.com/api/web_search` and acts as a fallback when the configured local Ollama search endpoint is unavailable. | | `OPENCLAUDE_NODE_MAX_OLD_SPACE_SIZE_MB` | No | Explicit V8 old-space heap cap in megabytes for the Node launcher. Used only when `--max-memory`, `--max-old-space-size-percentage`, and `OPENCLAUDE_NODE_MAX_OLD_SPACE_SIZE_PERCENTAGE` are all unset. Default `8192`. | -| `OPENCLAUDE_NODE_MAX_OLD_SPACE_SIZE_PERCENTAGE` | No | Size the V8 old-space heap as a percentage (greater than 0, up to 100) of constrained/container memory when Node reports it, otherwise total system RAM. Converted to `--max-old-space-size` so Node 22.0.0 can start. Precedence, earlier wins: `--max-memory` → `--max-old-space-size-percentage` → this variable → `OPENCLAUDE_NODE_MAX_OLD_SPACE_SIZE_MB` → default `8192`. | +| `OPENCLAUDE_NODE_MAX_OLD_SPACE_SIZE_PERCENTAGE` | No | Size the V8 old-space heap as a percentage (greater than 0, up to 100) of constrained/container memory when Node reports it, otherwise total system RAM. Converted to `--max-old-space-size` for compatibility across supported Node versions. Precedence, earlier wins: `--max-memory` → `--max-old-space-size-percentage` → this variable → `OPENCLAUDE_NODE_MAX_OLD_SPACE_SIZE_MB` → default `8192`. | | `CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS` | No | JSON map of OpenAI-compatible model names to context windows, such as `{"custom-model":1000000}`. Use this when a custom provider does not expose context metadata from `/v1/models`. | | `CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS` | No | JSON map of OpenAI-compatible model names to max output tokens, such as `{"custom-model":32768}`. Use this when a custom provider does not expose output-limit metadata from `/v1/models`. | | `OPENCODE_API_KEY` | OpenCode Zen / Go | Shared API key for OpenCode Zen (pay-as-you-go) and OpenCode Go (subscription); get yours from https://opencode.ai | diff --git a/docs/non-technical-setup.md b/docs/non-technical-setup.md index a5eff255e..ae55f3c4b 100644 --- a/docs/non-technical-setup.md +++ b/docs/non-technical-setup.md @@ -22,7 +22,7 @@ For most first-time users, OpenAI is the easiest option. You need: -1. Node.js 22 LTS or newer installed +1. Node.js 24.22.1 or newer (Node 26 recommended/default) installed 2. A terminal window 3. An API key from your provider, unless you are using a local model like Ollama diff --git a/docs/quick-start-mac-linux.md b/docs/quick-start-mac-linux.md index 03f80264e..bd065322a 100644 --- a/docs/quick-start-mac-linux.md +++ b/docs/quick-start-mac-linux.md @@ -4,7 +4,7 @@ This guide uses a standard shell such as Terminal, iTerm, bash, or zsh. ## 1. Install Node.js -Install Node.js 22 LTS or newer from: +Install Node.js 24.22.1 or newer (Node 26 recommended/default) from: - `https://nodejs.org/` diff --git a/docs/quick-start-windows.md b/docs/quick-start-windows.md index ca5bd8490..545d81a6b 100644 --- a/docs/quick-start-windows.md +++ b/docs/quick-start-windows.md @@ -4,7 +4,7 @@ This guide uses Windows PowerShell. ## 1. Install Node.js -Install Node.js 22 LTS or newer from: +Install Node.js 24.22.1 or newer (Node 26 recommended/default) from: - `https://nodejs.org/` diff --git a/package.json b/package.json index 0b81d98d0..6b21190c5 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "@smithy/core": "3.24.3", "@smithy/node-http-handler": "4.7.3", "@types/bun": "1.4.4", - "@types/node": "25.5.0", + "@types/node": "^24.19.2", "@types/react": "19.2.14", "@typescript-eslint/eslint-plugin": "^8.69.0", "@typescript-eslint/parser": "^8.69.0", @@ -191,7 +191,7 @@ } }, "engines": { - "node": ">=22.0.0" + "node": ">=24.22.1" }, "repository": { "type": "git", diff --git a/scripts/benchmark-openclaude-startup.mjs b/scripts/benchmark-openclaude-startup.mjs index e6ac2d05b..a1ea9357d 100644 --- a/scripts/benchmark-openclaude-startup.mjs +++ b/scripts/benchmark-openclaude-startup.mjs @@ -34,7 +34,7 @@ if (warmRuns < MIN_WARM_RUNS) { throw new Error(`--warm-runs must be at least ${MIN_WARM_RUNS}`) } if (typeof nodeModule.enableCompileCache !== 'function') { - throw new Error(`Node ${process.version} does not expose module.enableCompileCache; the benchmark requires Node >=22.8.0`) + throw new Error(`Node ${process.version} does not expose module.enableCompileCache; the benchmark requires Node >=24.22.1`) } if (!existsSync(BUNDLE_PATH) || !statSync(BUNDLE_PATH).isFile()) { throw new Error('dist/cli.mjs is missing; run `bun run build` first') diff --git a/scripts/externalsValidation.test.ts b/scripts/externalsValidation.test.ts index 685d8f2ab..70eb13624 100644 --- a/scripts/externalsValidation.test.ts +++ b/scripts/externalsValidation.test.ts @@ -304,7 +304,7 @@ describe('validateRuntimeDependencyContract', () => { }) describe('validateInstallHygieneFields', () => { - const CLEAN = { engines: { node: '>=22.0.0' } } + const CLEAN = { engines: { node: '>=24.22.1' } } test('passes for a clean manifest', () => { expect(validateInstallHygieneFields(CLEAN).ok).toBe(true) diff --git a/scripts/externalsValidation.ts b/scripts/externalsValidation.ts index 6a969704a..0ebc0fa79 100644 --- a/scripts/externalsValidation.ts +++ b/scripts/externalsValidation.ts @@ -40,7 +40,7 @@ export const RUNTIME_DEPENDENCY_CONTRACT: Readonly> = { } /** Node range advertised to installers; changing it changes who gets EBADENGINE. */ -export const ENGINES_NODE_CONTRACT = '>=22.0.0' +export const ENGINES_NODE_CONTRACT = '>=24.22.1' const EXACT_VERSION_RE = /^\d+\.\d+\.\d+(?:-[\w.]+)?$/ diff --git a/scripts/system-check.test.ts b/scripts/system-check.test.ts index dead9e1cd..45e8ed6f9 100644 --- a/scripts/system-check.test.ts +++ b/scripts/system-check.test.ts @@ -1048,14 +1048,14 @@ describe('checkNodeVersion', () => { test('reads the Node.js version from the node executable output', () => { const probe = readNodeExecutableVersion(() => ({ status: 0, - stdout: 'v22.0.0\n', + stdout: 'v24.22.1\n', stderr: '', error: undefined, })) expect(probe).toEqual({ ok: true, - version: 'v22.0.0', + version: 'v24.22.1', }) }) @@ -1064,7 +1064,7 @@ describe('checkNodeVersion', () => { ok: false, label: 'Node.js version', detail: - 'Detected 20.11.1. OpenClaude requires Node.js >=22.0.0. Install Node 22 LTS or newer, then reinstall/re-run OpenClaude.', + 'Detected 20.11.1. OpenClaude requires Node.js >=24.22.1. Install Node 24.22.1 or newer, then reinstall/re-run OpenClaude.', }) }) @@ -1080,7 +1080,7 @@ describe('checkNodeVersion', () => { ok: false, label: 'Node.js version', detail: - 'Unable to run `node --version`: spawn node ENOENT. OpenClaude requires Node.js >=22.0.0 on PATH.', + 'Unable to run `node --version`: spawn node ENOENT. OpenClaude requires Node.js >=24.22.1 on PATH.', }) }) @@ -1089,17 +1089,36 @@ describe('checkNodeVersion', () => { ok: false, label: 'Node.js version', detail: - 'Detected 20.11.1. OpenClaude requires Node.js >=22.0.0. Install Node 22 LTS or newer, then reinstall/re-run OpenClaude.', + 'Detected 20.11.1. OpenClaude requires Node.js >=24.22.1. Install Node 24.22.1 or newer, then reinstall/re-run OpenClaude.', }) }) test('passes supported Node.js versions', () => { - expect(checkNodeVersion('22.0.0')).toEqual({ + expect(checkNodeVersion('24.22.1')).toEqual({ ok: true, label: 'Node.js version', - detail: '22.0.0', + detail: '24.22.1', }) }) + + test('rejects the patch immediately below the Node.js minimum', () => { + expect(checkNodeVersion({ ok: true, version: 'v24.22.0' })).toEqual({ + ok: false, + label: 'Node.js version', + detail: + 'Detected 24.22.0. OpenClaude requires Node.js >=24.22.1. Install Node 24.22.1 or newer, then reinstall/re-run OpenClaude.', + }) + }) + + test.each(['v24.22.1', 'v26.12.1'])( + 'passes the supported probed version %s', version => { + expect(checkNodeVersion({ ok: true, version })).toEqual({ + ok: true, + label: 'Node.js version', + detail: version.slice(1), + }) + }, + ) }) describe('sandbox runtime diagnostics', () => { diff --git a/src/bootstrap/state.ts b/src/bootstrap/state.ts index 28486e5b4..c4c3a3cb9 100644 --- a/src/bootstrap/state.ts +++ b/src/bootstrap/state.ts @@ -407,7 +407,7 @@ const STATE: State = getInitialState() * * **Runtime Requirement:** Uses Node.js `async_hooks.AsyncLocalStorage`. * Not available in browsers or non-Node JavaScript environments. - * SDK consumers must run in OpenClaude's supported Node.js runtime (>=22.0.0). + * SDK consumers must run in OpenClaude's supported Node.js runtime (>=24.22.1). */ type SdkContext = { sessionId: SessionId diff --git a/src/entrypoints/cli.tsx b/src/entrypoints/cli.tsx index 27fd715b3..4f0ec5833 100644 --- a/src/entrypoints/cli.tsx +++ b/src/entrypoints/cli.tsx @@ -11,7 +11,7 @@ import { } from '../utils/printFlag.js' // Defensive compatibility guard for environments where globalThis.File is -// unexpectedly absent. OpenClaude's supported runtime is Node >=22; this is +// unexpectedly absent. OpenClaude's supported runtime is Node >=24.22.1; this is // not a Node 18 support guarantee. The guard is harmless on supported Node // versions and prevents undici's module evaluation from throwing in unusual // embedded/runtime setups. diff --git a/src/utils/interruptionTrace.ts b/src/utils/interruptionTrace.ts index 3999505d9..1c148ad1a 100644 --- a/src/utils/interruptionTrace.ts +++ b/src/utils/interruptionTrace.ts @@ -1,9 +1,6 @@ import { createHash, randomUUID } from 'node:crypto' import { isAbsolute, posix, win32 } from 'node:path' -import { - monitorEventLoopDelay, - type IntervalHistogram, -} from 'node:perf_hooks' +import { monitorEventLoopDelay } from 'node:perf_hooks' import { normalizeAbortReason } from './abortReasons.js' import { appendDiagnosticsNoPII } from './diagLogs.js' import { redactHomePath, redactLikelySecrets } from './redaction.js' @@ -117,7 +114,7 @@ let signalAbortEventIds = new WeakMap() let signalAbortSources = new WeakMap() let signalStates = new WeakMap() let errorCausalEventIds = new WeakMap() -let eventLoopDelay: IntervalHistogram | undefined +let eventLoopDelay: ReturnType | undefined let flushQueue: Promise = Promise.resolve() let retryBatch: | { diff --git a/src/utils/nodeRuntime.test.ts b/src/utils/nodeRuntime.test.ts index 79b04f6a4..674954295 100644 --- a/src/utils/nodeRuntime.test.ts +++ b/src/utils/nodeRuntime.test.ts @@ -13,27 +13,27 @@ describe('node runtime contract', () => { new URL('../../package.json', import.meta.url), ).json() - expect(MIN_NODE_MAJOR).toBe(22) - expect(MIN_NODE_VERSION).toBe('22.0.0') - expect(MIN_NODE_ENGINE_RANGE).toBe('>=22.0.0') + expect(MIN_NODE_MAJOR).toBe(24) + expect(MIN_NODE_VERSION).toBe('24.22.1') + expect(MIN_NODE_ENGINE_RANGE).toBe('>=24.22.1') expect(packageJson.engines.node).toBe(MIN_NODE_ENGINE_RANGE) }) test('accepts supported Node versions', () => { - expect(checkSupportedNodeVersion('v22.0.0')).toEqual({ + expect(checkSupportedNodeVersion('v24.22.1')).toEqual({ ok: true, - version: '22.0.0', - major: 22, + version: '24.22.1', + major: 24, }) - expect(checkSupportedNodeVersion('22.0.0')).toEqual({ + expect(checkSupportedNodeVersion('24.22.1')).toEqual({ ok: true, - version: '22.0.0', - major: 22, + version: '24.22.1', + major: 24, }) - expect(checkSupportedNodeVersion('25.5.0')).toEqual({ + expect(checkSupportedNodeVersion('26.12.1')).toEqual({ ok: true, - version: '25.5.0', - major: 25, + version: '26.12.1', + major: 26, }) }) @@ -43,17 +43,38 @@ describe('node runtime contract', () => { version: '20.11.1', major: 20, message: - 'Detected 20.11.1. OpenClaude requires Node.js >=22.0.0. Install Node 22 LTS or newer, then reinstall/re-run OpenClaude.', + 'Detected 20.11.1. OpenClaude requires Node.js >=24.22.1. Install Node 24.22.1 or newer, then reinstall/re-run OpenClaude.', }) }) + test.each(['23.12.0', '24.21.9', '24.22.0', '24.22.1-rc.1'])( + 'rejects %s below the exact minimum', version => { + expect(checkSupportedNodeVersion(version)).toEqual({ + ok: false, + version, + major: Number(version.split('.')[0]), + message: `Detected ${version}. OpenClaude requires Node.js >=24.22.1. Install Node 24.22.1 or newer, then reinstall/re-run OpenClaude.`, + }) + }, + ) + + test.each(['24.22.1', '24.22.2', '24.23.0', '26.0.0', '26.12.1'])( + 'accepts %s at or above the exact minimum', version => { + expect(checkSupportedNodeVersion(version)).toEqual({ + ok: true, + version, + major: Number(version.split('.')[0]), + }) + }, + ) + test('rejects malformed Node versions with the same required minimum', () => { expect(checkSupportedNodeVersion('nightly')).toEqual({ ok: false, version: 'nightly', major: null, message: - 'Could not parse Node.js version: nightly. OpenClaude requires Node.js >=22.0.0.', + 'Could not parse Node.js version: nightly. OpenClaude requires Node.js >=24.22.1.', }) }) }) diff --git a/src/utils/nodeRuntime.ts b/src/utils/nodeRuntime.ts index be1fb6eba..eba3c03fd 100644 --- a/src/utils/nodeRuntime.ts +++ b/src/utils/nodeRuntime.ts @@ -1,5 +1,7 @@ -export const MIN_NODE_MAJOR = 22 -export const MIN_NODE_VERSION = '22.0.0' +import { satisfies } from './semver.js' + +export const MIN_NODE_MAJOR = 24 +export const MIN_NODE_VERSION = '24.22.1' export const MIN_NODE_ENGINE_RANGE = `>=${MIN_NODE_VERSION}` export type NodeVersionCheckResult = @@ -39,12 +41,12 @@ export function checkSupportedNodeVersion( } } - if (major < MIN_NODE_MAJOR) { + if (!satisfies(version, MIN_NODE_ENGINE_RANGE)) { return { ok: false, version, major, - message: `Detected ${version}. OpenClaude requires Node.js ${MIN_NODE_ENGINE_RANGE}. Install Node ${MIN_NODE_MAJOR} LTS or newer, then reinstall/re-run OpenClaude.`, + message: `Detected ${version}. OpenClaude requires Node.js ${MIN_NODE_ENGINE_RANGE}. Install Node ${MIN_NODE_VERSION} or newer, then reinstall/re-run OpenClaude.`, } } diff --git a/src/utils/readFileInRange.ts b/src/utils/readFileInRange.ts index 5268ab792..c5a5b7619 100644 --- a/src/utils/readFileInRange.ts +++ b/src/utils/readFileInRange.ts @@ -237,7 +237,8 @@ function streamOnOpen(this: StreamState, fd: number): void { }) } -function streamOnData(this: StreamState, chunk: string): void { +function streamOnData(this: StreamState, chunk: string | Buffer): void { + chunk = typeof chunk === 'string' ? chunk : chunk.toString('utf8') if (this.isFirstChunk) { this.isFirstChunk = false if (chunk.charCodeAt(0) === 0xfeff) { diff --git a/src/utils/withResolvers.ts b/src/utils/withResolvers.ts index e7f261ee6..181df3a2a 100644 --- a/src/utils/withResolvers.ts +++ b/src/utils/withResolvers.ts @@ -1,6 +1,6 @@ /** * Polyfill for Promise.withResolvers() (ES2024, Node 22+). - * Native on every supported runtime now that engines.node is >=22.0.0; kept as a + * Native on every supported runtime (engines.node >=24.22.1); kept as a * thin wrapper so call sites stay stable and downstream embeds running older Node * (despite the warning) don't crash. */ diff --git a/web/public/llms.txt b/web/public/llms.txt index 6c3725bf8..ec38db6cc 100644 --- a/web/public/llms.txt +++ b/web/public/llms.txt @@ -18,7 +18,7 @@ OpenClaude originated from the Claude Code codebase and has since been substanti - [Quick start (Windows)](https://github.com/Twigpine/openclaude/blob/main/docs/quick-start-windows.md) - [Non-technical setup](https://github.com/Twigpine/openclaude/blob/main/docs/non-technical-setup.md) - [Android install](https://github.com/Twigpine/openclaude/blob/main/ANDROID_INSTALL.md) -- [npm install](https://www.npmjs.com/package/@twigpine/openclaude): `npm install -g @twigpine/openclaude@latest` (requires Node >= 22) +- [npm install](https://www.npmjs.com/package/@twigpine/openclaude): `npm install -g @twigpine/openclaude@latest` (requires Node >= 24.22.1; Node 26 recommended/default) - [AUR package: openclaude](https://aur.archlinux.org/packages/openclaude) ## Documentation diff --git a/web/src/pages/docs/installation.astro b/web/src/pages/docs/installation.astro index 656d078ac..36cab7263 100644 --- a/web/src/pages/docs/installation.astro +++ b/web/src/pages/docs/installation.astro @@ -22,7 +22,7 @@ const toc = [ >

requirements

    -
  • Node.js 22 or newer — grab it from nodejs.org and confirm with node --version.
  • +
  • Node.js 24.22.1 or newer (Node 26 recommended/default) — grab it from nodejs.org and confirm with node --version.
  • A terminal: Terminal/iTerm on macOS, any shell on Linux, PowerShell or Windows Terminal on Windows.
  • ripgrep is bundled; if startup reports ripgrep not found, install it system-wide and confirm rg --version works.
diff --git a/web/src/pages/index.astro b/web/src/pages/index.astro index f4450d114..755c6dc2b 100644 --- a/web/src/pages/index.astro +++ b/web/src/pages/index.astro @@ -216,7 +216,7 @@ const jsonLd = [ 01
install -

requires node ≥ 22. platform guides →

+

requires node ≥ 24.22.1; node 26 recommended/default. platform guides →