From afc96cd62ff2a3b16bab131e0609f2bc6bc392f9 Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Fri, 5 Jun 2026 19:31:55 +0200 Subject: [PATCH 01/17] feat: resolve prebuilt @node-llama-cpp/linux-riscv64 binary Signed-off-by: Bruno Verachten --- package.json | 1 + src/bindings/utils/compileLLamaCpp.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/package.json b/package.json index 1ca551da..777b4720 100644 --- a/package.json +++ b/package.json @@ -226,6 +226,7 @@ "optionalDependencies": { "@node-llama-cpp/linux-arm64": "0.1.0", "@node-llama-cpp/linux-armv7l": "0.1.0", + "@node-llama-cpp/linux-riscv64": "0.1.0", "@node-llama-cpp/linux-x64": "0.1.0", "@node-llama-cpp/linux-x64-cuda": "0.1.0", "@node-llama-cpp/linux-x64-cuda-ext": "0.1.0", diff --git a/src/bindings/utils/compileLLamaCpp.ts b/src/bindings/utils/compileLLamaCpp.ts index 6857890b..2875fbad 100644 --- a/src/bindings/utils/compileLLamaCpp.ts +++ b/src/bindings/utils/compileLLamaCpp.ts @@ -618,6 +618,9 @@ function getPrebuiltBinariesPackageDirectoryForBuildOptions(buildOptions: { else if (buildOptions.arch === "arm") // @ts-ignore return getBinariesPathFromModules(() => import("@node-llama-cpp/linux-armv7l")); + else if (buildOptions.arch === "riscv64") + // @ts-ignore + return getBinariesPathFromModules(() => import("@node-llama-cpp/linux-riscv64")); } else if (buildOptions.platform === "win") { if (buildOptions.arch === "x64") { if (buildOptions.gpu === "cuda") From 44e844c8230cf64e60079dfea88dcc2435e4bdf2 Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Fri, 5 Jun 2026 19:44:06 +0200 Subject: [PATCH 02/17] ci: build riscv64 prebuilt binary natively on the RISE runner Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 55540edc..6b33e8d3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,13 +75,28 @@ jobs: - name: "macOS arm64" os: macos-26 artifact: "mac-arm64" + - name: "Ubuntu riscv64" + os: ubuntu-24.04-riscv + artifact: "linux-riscv64" steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 + if: matrix.config.artifact != 'linux-riscv64' with: node-version: "20" + - name: Install Node.js (riscv64 unofficial build) + if: matrix.config.artifact == 'linux-riscv64' + run: | + set -e + NODE_VER=v20.19.0 + curl -fsSL -o /tmp/node.tar.xz "https://unofficial-builds.nodejs.org/download/release/${NODE_VER}/node-${NODE_VER}-linux-riscv64.tar.xz" + mkdir -p "$HOME/node" + tar -xJf /tmp/node.tar.xz -C "$HOME/node" --strip-components=1 + echo "$HOME/node/bin" >> "$GITHUB_PATH" + "$HOME/node/bin/node" --version + - name: Download build artifact uses: actions/download-artifact@v4 with: @@ -131,6 +146,13 @@ jobs: cmake --version + - name: Install dependencies on Ubuntu riscv64 + if: matrix.config.name == 'Ubuntu riscv64' + run: | + sudo apt-get update + sudo apt-get install -y cmake build-essential ninja-build libtbb-dev + cmake --version + - name: Install Cuda 13.1 on Windows (1) if: matrix.config.name == 'Windows (1)' uses: Jimver/cuda-toolkit@v0.2.30 @@ -275,6 +297,8 @@ jobs: await buildBinary("x64", ["--gpu", "false"]); } else if (process.env.ARTIFACT_NAME === "mac-arm64") { await buildBinary("arm64", ["--gpu", "metal"]); + } else if (process.env.ARTIFACT_NAME === "linux-riscv64") { + await buildBinary("riscv64", ["--gpu", "false"]); } // move binaries to bins From 362c8ff5ec7f0d2e555ad82c9a72661a51051039 Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Fri, 5 Jun 2026 20:11:41 +0200 Subject: [PATCH 03/17] feat: package @node-llama-cpp/linux-riscv64 and detect riscv64 glibc Signed-off-by: Bruno Verachten --- .../@node-llama-cpp/linux-riscv64/.gitignore | 1 + .../@node-llama-cpp/linux-riscv64/LICENSE | 21 ++++++++ .../@node-llama-cpp/linux-riscv64/README.md | 4 ++ .../linux-riscv64/package-lock.json | 39 ++++++++++++++ .../linux-riscv64/package.json | 54 +++++++++++++++++++ .../linux-riscv64/src/index.ts | 14 +++++ .../linux-riscv64/tsconfig.json | 34 ++++++++++++ ...movePrebuiltBinariesToStandaloneModules.ts | 1 + src/bindings/utils/detectGlibc.ts | 9 +++- 9 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 packages/@node-llama-cpp/linux-riscv64/.gitignore create mode 100644 packages/@node-llama-cpp/linux-riscv64/LICENSE create mode 100644 packages/@node-llama-cpp/linux-riscv64/README.md create mode 100644 packages/@node-llama-cpp/linux-riscv64/package-lock.json create mode 100644 packages/@node-llama-cpp/linux-riscv64/package.json create mode 100644 packages/@node-llama-cpp/linux-riscv64/src/index.ts create mode 100644 packages/@node-llama-cpp/linux-riscv64/tsconfig.json diff --git a/packages/@node-llama-cpp/linux-riscv64/.gitignore b/packages/@node-llama-cpp/linux-riscv64/.gitignore new file mode 100644 index 00000000..9b1c8b13 --- /dev/null +++ b/packages/@node-llama-cpp/linux-riscv64/.gitignore @@ -0,0 +1 @@ +/dist diff --git a/packages/@node-llama-cpp/linux-riscv64/LICENSE b/packages/@node-llama-cpp/linux-riscv64/LICENSE new file mode 100644 index 00000000..22789ae3 --- /dev/null +++ b/packages/@node-llama-cpp/linux-riscv64/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Gilad S. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/@node-llama-cpp/linux-riscv64/README.md b/packages/@node-llama-cpp/linux-riscv64/README.md new file mode 100644 index 00000000..39501c59 --- /dev/null +++ b/packages/@node-llama-cpp/linux-riscv64/README.md @@ -0,0 +1,4 @@ +# [`node-llama-cpp`](https://github.com/withcatai/node-llama-cpp) +This is a prebuilt binary package for [`node-llama-cpp`](https://github.com/withcatai/node-llama-cpp) for Linux riscv64. + +Do not install this package directly. diff --git a/packages/@node-llama-cpp/linux-riscv64/package-lock.json b/packages/@node-llama-cpp/linux-riscv64/package-lock.json new file mode 100644 index 00000000..db2b4d6e --- /dev/null +++ b/packages/@node-llama-cpp/linux-riscv64/package-lock.json @@ -0,0 +1,39 @@ +{ + "name": "@node-llama-cpp/linux-riscv64", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@node-llama-cpp/linux-riscv64", + "version": "0.1.0", + "cpu": [ + "arm", + "x64" + ], + "license": "MIT", + "os": [ + "linux" + ], + "devDependencies": { + "typescript": "^5.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/typescript": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/packages/@node-llama-cpp/linux-riscv64/package.json b/packages/@node-llama-cpp/linux-riscv64/package.json new file mode 100644 index 00000000..00d03580 --- /dev/null +++ b/packages/@node-llama-cpp/linux-riscv64/package.json @@ -0,0 +1,54 @@ +{ + "name": "@node-llama-cpp/linux-riscv64", + "version": "0.1.0", + "description": "Prebuilt binary for node-llama-cpp for Linux riscv64", + "main": "dist/index.js", + "type": "module", + "files": [ + "dist/", + "bins/", + "package.json", + "README.md", + "LICENSE" + ], + "exports": { + ".": { + "import": "./dist/index.js", + "node": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "engines": { + "node": ">=20.0.0" + }, + "os": [ + "linux" + ], + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "scripts": { + "prebuild": "rimraf ./dist ./tsconfig.tsbuildinfo", + "build": "tsc --build tsconfig.json --force", + "prewatch": "rimraf ./dist ./tsconfig.tsbuildinfo", + "watch": "tsc --build tsconfig.json --watch --force", + "clean": "rm -rf ./node_modules ./dist ./tsconfig.tsbuildinfo" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/withcatai/node-llama-cpp.git" + }, + "author": "Gilad S.", + "license": "MIT", + "preferUnplugged": true, + "bugs": { + "url": "https://github.com/withcatai/node-llama-cpp/issues" + }, + "homepage": "https://node-llama-cpp.withcat.ai", + "devDependencies": { + "typescript": "^5.2.2" + } +} diff --git a/packages/@node-llama-cpp/linux-riscv64/src/index.ts b/packages/@node-llama-cpp/linux-riscv64/src/index.ts new file mode 100644 index 00000000..a4cb56d5 --- /dev/null +++ b/packages/@node-llama-cpp/linux-riscv64/src/index.ts @@ -0,0 +1,14 @@ +import path from "path"; +import {fileURLToPath} from "url"; +import fs from "node:fs/promises"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const binsDir = path.join(__dirname, "..", "bins"); +const packageVersion: string = (JSON.parse(await fs.readFile(path.join(__dirname, "..", "package.json"), "utf8"))).version; + +export function getBinsDir() { + return { + binsDir, + packageVersion + }; +} diff --git a/packages/@node-llama-cpp/linux-riscv64/tsconfig.json b/packages/@node-llama-cpp/linux-riscv64/tsconfig.json new file mode 100644 index 00000000..527d791c --- /dev/null +++ b/packages/@node-llama-cpp/linux-riscv64/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "lib": ["es2022"], + "module": "node16", + "target": "es2022", + "esModuleInterop": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitOverride": true, + "removeComments": false, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "moduleResolution": "node16", + "resolveJsonModule": false, + "strictNullChecks": true, + "isolatedModules": true, + "noEmit": false, + "outDir": "./dist", + "strict": true, + "sourceMap": false, + "composite": false, + "declaration": false, + "stripInternal": true + }, + "files": [ + "./src/index.ts" + ], + "include": [ + "./src" + ] +} diff --git a/scripts/movePrebuiltBinariesToStandaloneModules.ts b/scripts/movePrebuiltBinariesToStandaloneModules.ts index 792b8fb9..5eb1dafb 100644 --- a/scripts/movePrebuiltBinariesToStandaloneModules.ts +++ b/scripts/movePrebuiltBinariesToStandaloneModules.ts @@ -58,6 +58,7 @@ await moveBinariesFolderToStandaloneModule((folderName) => folderName.startsWith await moveBinariesFolderToStandaloneModule((folderName) => folderName.startsWith("linux-arm64"), "@node-llama-cpp/linux-arm64"); await moveBinariesFolderToStandaloneModule((folderName) => folderName.startsWith("linux-armv7l"), "@node-llama-cpp/linux-armv7l"); +await moveBinariesFolderToStandaloneModule((folderName) => folderName.startsWith("linux-riscv64"), "@node-llama-cpp/linux-riscv64"); await moveBinariesFallbackDirToStandaloneExtModule((folderName) => folderName.startsWith("win-x64-cuda"), "@node-llama-cpp/win-x64-cuda-ext"); await moveBinariesFolderToStandaloneModule((folderName) => folderName.startsWith("win-x64-cuda"), "@node-llama-cpp/win-x64-cuda"); diff --git a/src/bindings/utils/detectGlibc.ts b/src/bindings/utils/detectGlibc.ts index 416ce8e0..5424e808 100644 --- a/src/bindings/utils/detectGlibc.ts +++ b/src/bindings/utils/detectGlibc.ts @@ -19,7 +19,8 @@ export async function detectGlibc({ "/usr/lib64", "/usr/lib/x86_64-linux-gnu", "/usr/lib/aarch64-linux-gnu", - "/usr/lib/armv7l-linux-gnu" + "/usr/lib/armv7l-linux-gnu", + "/usr/lib/riscv64-linux-gnu" ]; const glibcFileNames = [ @@ -45,7 +46,11 @@ export async function detectGlibc({ "ld-linux-armv7l.so", "ld-linux-armv7l.so.1", "ld-linux-armv7l.so.2", - "ld-linux-armv7l.so.3" // for when the next version comes out + "ld-linux-armv7l.so.3", // for when the next version comes out + "ld-linux-riscv64-lp64d.so", + "ld-linux-riscv64-lp64d.so.1", + "ld-linux-riscv64-lp64d.so.2", + "ld-linux-riscv64-lp64d.so.3" // for when the next version comes out ]; const foundGlibC = await asyncEvery([ From 4342c578e736fc13c1b2adb19533144cb4bd94e8 Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Fri, 5 Jun 2026 20:31:26 +0200 Subject: [PATCH 04/17] build: add @node-llama-cpp/linux-riscv64 to package-lock Signed-off-by: Bruno Verachten --- package-lock.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package-lock.json b/package-lock.json index 34dbed59..a1c02768 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,6 +103,7 @@ "optionalDependencies": { "@node-llama-cpp/linux-arm64": "0.1.0", "@node-llama-cpp/linux-armv7l": "0.1.0", + "@node-llama-cpp/linux-riscv64": "0.1.0", "@node-llama-cpp/linux-x64": "0.1.0", "@node-llama-cpp/linux-x64-cuda": "0.1.0", "@node-llama-cpp/linux-x64-cuda-ext": "0.1.0", @@ -2378,6 +2379,9 @@ "node_modules/@node-llama-cpp/linux-armv7l": { "optional": true }, + "node_modules/@node-llama-cpp/linux-riscv64": { + "optional": true + }, "node_modules/@node-llama-cpp/linux-x64": { "optional": true }, From aa9fd7f7812b3da492cf38287c7d07f1b3f4515f Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Fri, 5 Jun 2026 21:00:37 +0200 Subject: [PATCH 05/17] ci: use node 24 on riscv64 runner (npm 11 fixes npm ci) Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b33e8d3..543bce45 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,10 @@ jobs: if: matrix.config.artifact == 'linux-riscv64' run: | set -e - NODE_VER=v20.19.0 + # node 24 (npm 11): node 20's npm 10.8.2 trips "set.delete is not a + # function" during npm ci on riscv64, and several devDeps require + # node >= 24.10. The built binary still targets node 20 N-API. + NODE_VER=v24.16.0 curl -fsSL -o /tmp/node.tar.xz "https://unofficial-builds.nodejs.org/download/release/${NODE_VER}/node-${NODE_VER}-linux-riscv64.tar.xz" mkdir -p "$HOME/node" tar -xJf /tmp/node.tar.xz -C "$HOME/node" --strip-components=1 From be14bfe2070940f3d961336c7fda698984de39af Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Fri, 5 Jun 2026 21:28:36 +0200 Subject: [PATCH 06/17] ci: skip electron binary download on riscv64 (no prebuilt) Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 543bce45..bf799b53 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -232,6 +232,10 @@ jobs: timeout-minutes: 300 env: ARTIFACT_NAME: ${{ matrix.config.artifact }} + # electron has no riscv64 prebuilt binary; the binary build does not + # use it (the electron example is a separate job), so skip its + # postinstall download to let npm ci complete on riscv64. + ELECTRON_SKIP_BINARY_DOWNLOAD: ${{ matrix.config.artifact == 'linux-riscv64' && '1' || '' }} run: | npm ci From bf8fe227ec4de00735bddf7fe27d79934fd9ce00 Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Fri, 5 Jun 2026 22:09:51 +0200 Subject: [PATCH 07/17] ci: retry npm ci on riscv64 (intermittent npm TypeErrors) Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bf799b53..c6d1972c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -237,8 +237,10 @@ jobs: # postinstall download to let npm ci complete on riscv64. ELECTRON_SKIP_BINARY_DOWNLOAD: ${{ matrix.config.artifact == 'linux-riscv64' && '1' || '' }} run: | - npm ci - + # npm on the unofficial riscv64 node intermittently throws internal + # TypeErrors (e.g. "key.match is not a function"); retry a few times. + npm ci || npm ci || npm ci + npx zx -y <<'EOF' async function getLatestNodeVersions(maxDate) { From d3163b4486c92ec2df7aff878ed380cb78f18465 Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Sat, 6 Jun 2026 09:32:53 +0200 Subject: [PATCH 08/17] ci: use gcc-14 for riscv64 native build (zvfh f16 intrinsics) Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c6d1972c..71a04044 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -153,8 +153,14 @@ jobs: if: matrix.config.name == 'Ubuntu riscv64' run: | sudo apt-get update - sudo apt-get install -y cmake build-essential ninja-build libtbb-dev + sudo apt-get install -y cmake build-essential ninja-build libtbb-dev gcc-14 g++-14 + # llama.cpp's ggml-cpu uses the RVV _Float16 (zvfh) vector intrinsics, + # which only exist in GCC 14+. The runner defaults to GCC 13, so make + # gcc-14/g++-14 the default compilers (same as upstream build-riscv). + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 cmake --version + gcc --version - name: Install Cuda 13.1 on Windows (1) if: matrix.config.name == 'Windows (1)' From 22adbb7ab71b4f2b7f75dc9b7a032a89a177b7c2 Mon Sep 17 00:00:00 2001 From: "Gilad S." <7817232+giladgd@users.noreply.github.com> Date: Tue, 16 Jun 2026 20:49:16 +0300 Subject: [PATCH 09/17] fix: build both RVV and non-RVV builds in the CI --- src/bindings/utils/compileLLamaCpp.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bindings/utils/compileLLamaCpp.ts b/src/bindings/utils/compileLLamaCpp.ts index 2875fbad..56dbebcb 100644 --- a/src/bindings/utils/compileLLamaCpp.ts +++ b/src/bindings/utils/compileLLamaCpp.ts @@ -163,7 +163,9 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions if (!cmakeCustomOptions.has("GGML_NATIVE") || isCmakeValueOff(cmakeCustomOptions.get("GGML_NATIVE"))) { cmakeCustomOptions.set("GGML_NATIVE", "OFF"); - if (buildOptions.arch === "x64" && !cmakeCustomOptions.has("GGML_CPU_ALL_VARIANTS")) { + if ((buildOptions.arch === "x64" || buildOptions.arch === "riscv64") && + !cmakeCustomOptions.has("GGML_CPU_ALL_VARIANTS") + ) { cmakeCustomOptions.set("GGML_CPU_ALL_VARIANTS", "ON"); cmakeCustomOptions.set("GGML_BACKEND_DL", "ON"); } else if (!cmakeCustomOptions.has("GGML_BACKEND_DL")) From 6528b6e868496a7ae895a39bed5af036e7d48ad5 Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Tue, 16 Jun 2026 20:05:27 +0200 Subject: [PATCH 10/17] ci: always skip electron binary download in build job The build job only uses electron's TypeScript types, never its binary, so skip the postinstall download unconditionally rather than only on riscv64. This also keeps npm ci working on riscv64, where electron has no prebuilt. Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 71a04044..c0b07730 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -238,10 +238,10 @@ jobs: timeout-minutes: 300 env: ARTIFACT_NAME: ${{ matrix.config.artifact }} - # electron has no riscv64 prebuilt binary; the binary build does not - # use it (the electron example is a separate job), so skip its - # postinstall download to let npm ci complete on riscv64. - ELECTRON_SKIP_BINARY_DOWNLOAD: ${{ matrix.config.artifact == 'linux-riscv64' && '1' || '' }} + # This job only needs electron's types, never its binary, so always + # skip the postinstall download. It also unblocks npm ci on riscv64, + # where electron ships no prebuilt binary. + ELECTRON_SKIP_BINARY_DOWNLOAD: "1" run: | # npm on the unofficial riscv64 node intermittently throws internal # TypeErrors (e.g. "key.match is not a function"); retry a few times. From e76c8bb06332967eedb103e96e44d2889547ac6b Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Tue, 16 Jun 2026 20:59:37 +0200 Subject: [PATCH 11/17] ci: document upstream cause of riscv64 npm ci retry Note that the intermittent npm ci TypeErrors on riscv64 stem from upstream V8-on-riscv64 flakiness rather than an npm or repo bug, so the retry is a deliberate workaround for non-deterministic upstream behaviour. Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0b07730..ae7f96db 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -243,8 +243,10 @@ jobs: # where electron ships no prebuilt binary. ELECTRON_SKIP_BINARY_DOWNLOAD: "1" run: | - # npm on the unofficial riscv64 node intermittently throws internal - # TypeErrors (e.g. "key.match is not a function"); retry a few times. + # The unofficial riscv64 Node runtime intermittently throws internal + # TypeErrors during npm ci (e.g. "key.match is not a function"). These + # trace to upstream V8-on-riscv64 flakiness, not an npm or repo bug, + # and don't reproduce deterministically, so retry a few times. npm ci || npm ci || npm ci npx zx -y <<'EOF' From ac456d207e0773763162f013dc00091c036a5034 Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Wed, 17 Jun 2026 09:25:52 +0200 Subject: [PATCH 12/17] ci: scope riscv64 npm ci retry and tidy build.yml comments Wrap the npm ci retry in an $ARTIFACT_NAME check so only the riscv64 leg retries; other platforms run a plain npm ci and fail on the first try. Move the gcc-14 RVV comment above the step and drop the now-redundant ELECTRON_SKIP_BINARY_DOWNLOAD comment. Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae7f96db..69de5d15 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -149,14 +149,14 @@ jobs: cmake --version + # llama.cpp's ggml-cpu uses the RVV _Float16 (zvfh) vector intrinsics, + # which only exist in GCC 14+. The runner defaults to GCC 13, so make + # gcc-14/g++-14 the default compilers (same as upstream build-riscv). - name: Install dependencies on Ubuntu riscv64 if: matrix.config.name == 'Ubuntu riscv64' run: | sudo apt-get update sudo apt-get install -y cmake build-essential ninja-build libtbb-dev gcc-14 g++-14 - # llama.cpp's ggml-cpu uses the RVV _Float16 (zvfh) vector intrinsics, - # which only exist in GCC 14+. The runner defaults to GCC 13, so make - # gcc-14/g++-14 the default compilers (same as upstream build-riscv). sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 cmake --version @@ -238,16 +238,19 @@ jobs: timeout-minutes: 300 env: ARTIFACT_NAME: ${{ matrix.config.artifact }} - # This job only needs electron's types, never its binary, so always - # skip the postinstall download. It also unblocks npm ci on riscv64, - # where electron ships no prebuilt binary. ELECTRON_SKIP_BINARY_DOWNLOAD: "1" run: | - # The unofficial riscv64 Node runtime intermittently throws internal - # TypeErrors during npm ci (e.g. "key.match is not a function"). These - # trace to upstream V8-on-riscv64 flakiness, not an npm or repo bug, - # and don't reproduce deterministically, so retry a few times. - npm ci || npm ci || npm ci + if [ "$ARTIFACT_NAME" = "linux-riscv64" ]; then + # The unofficial riscv64 Node runtime intermittently throws internal + # TypeErrors during npm ci (e.g. "key.match is not a function"). These + # trace to upstream V8-on-riscv64 flakiness, not an npm or repo bug, + # and don't reproduce deterministically, so retry a few times. Keep + # the retry scoped to riscv64 so a first-try failure on any other + # platform still fails loudly. + npm ci || npm ci || npm ci + else + npm ci + fi npx zx -y <<'EOF' From b5df301156f56e44898dd7367fb143f04296cc5f Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Wed, 17 Jun 2026 21:24:48 +0200 Subject: [PATCH 13/17] ci: prefer preinstalled Node 24 on riscv64; cleaner npm ci retry ubuntu-24.04-riscv ships Node 24, so use it when present and recent enough, falling back to the unofficial build only if Node is missing or older than 24.10 (actions/setup-node can't help: actions/node-versions has no riscv64 entries). Replace the npm ci ||-chain with a retry loop that still fails the job if all attempts fail. Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69de5d15..51ce77d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,9 +90,22 @@ jobs: if: matrix.config.artifact == 'linux-riscv64' run: | set -e - # node 24 (npm 11): node 20's npm 10.8.2 trips "set.delete is not a - # function" during npm ci on riscv64, and several devDeps require + # node 24 (npm 11) is required: node 20's npm 10.8.2 trips "set.delete + # is not a function" during npm ci on riscv64, and several devDeps need # node >= 24.10. The built binary still targets node 20 N-API. + # ubuntu-24.04-riscv ships node 24, so prefer the preinstalled one and + # only fall back to the unofficial build when it is missing or too old. + # (actions/setup-node can't help here: actions/node-versions has no + # riscv64 entries in its versions manifest.) + need=24.10.0 + if command -v node >/dev/null 2>&1; then + cur="$(node -p 'process.versions.node')" + if [ "$(printf '%s\n%s\n' "$need" "$cur" | sort -V | head -1)" = "$need" ]; then + echo "Using preinstalled Node.js v$cur at $(command -v node)" + exit 0 + fi + echo "Preinstalled Node.js v$cur is older than $need; installing unofficial build" + fi NODE_VER=v24.16.0 curl -fsSL -o /tmp/node.tar.xz "https://unofficial-builds.nodejs.org/download/release/${NODE_VER}/node-${NODE_VER}-linux-riscv64.tar.xz" mkdir -p "$HOME/node" @@ -247,7 +260,16 @@ jobs: # and don't reproduce deterministically, so retry a few times. Keep # the retry scoped to riscv64 so a first-try failure on any other # platform still fails loudly. - npm ci || npm ci || npm ci + n=0 + until npm ci; do + n=$((n + 1)) + if [ "$n" -ge 3 ]; then + echo "npm ci failed after $n attempts" + exit 1 + fi + echo "npm ci attempt $n failed, retrying in 5s" + sleep 5 + done else npm ci fi From 686ca7a7b61a54a1cbadac11df281b4b132ae5a1 Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Thu, 18 Jun 2026 08:57:08 +0200 Subject: [PATCH 14/17] ci: use retry-cli for riscv64 npm ci retry Replace the hand-rolled until-loop with retry-cli (exponential backoff), as suggested by the maintainers. Still scoped to linux-riscv64 so a first-try failure on any other platform fails loudly. Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 51ce77d6..cee5b75b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -257,19 +257,10 @@ jobs: # The unofficial riscv64 Node runtime intermittently throws internal # TypeErrors during npm ci (e.g. "key.match is not a function"). These # trace to upstream V8-on-riscv64 flakiness, not an npm or repo bug, - # and don't reproduce deterministically, so retry a few times. Keep + # and don't reproduce deterministically, so retry with backoff. Keep # the retry scoped to riscv64 so a first-try failure on any other # platform still fails loudly. - n=0 - until npm ci; do - n=$((n + 1)) - if [ "$n" -ge 3 ]; then - echo "npm ci failed after $n attempts" - exit 1 - fi - echo "npm ci attempt $n failed, retrying in 5s" - sleep 5 - done + npx -y retry-cli -n 5 -- npm ci else npm ci fi From 283c1170c27888b9ec10d2449abf00b1a81cadd0 Mon Sep 17 00:00:00 2001 From: "Gilad S." <7817232+giladgd@users.noreply.github.com> Date: Fri, 19 Jun 2026 20:43:29 +0300 Subject: [PATCH 15/17] fix: use builtin `retry` CLI --- .github/workflows/build.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cee5b75b..fc67415f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -255,12 +255,8 @@ jobs: run: | if [ "$ARTIFACT_NAME" = "linux-riscv64" ]; then # The unofficial riscv64 Node runtime intermittently throws internal - # TypeErrors during npm ci (e.g. "key.match is not a function"). These - # trace to upstream V8-on-riscv64 flakiness, not an npm or repo bug, - # and don't reproduce deterministically, so retry with backoff. Keep - # the retry scoped to riscv64 so a first-try failure on any other - # platform still fails loudly. - npx -y retry-cli -n 5 -- npm ci + # TypeErrors during npm ci (e.g. "key.match is not a function") + retry --times=3 npm ci else npm ci fi From eb277c80acedca2f712cfeeb57e68bfd65f78533 Mon Sep 17 00:00:00 2001 From: "Gilad S." <7817232+giladgd@users.noreply.github.com> Date: Fri, 19 Jun 2026 20:52:55 +0300 Subject: [PATCH 16/17] fix: remove long comment --- .github/workflows/build.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fc67415f..b28ca504 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,13 +90,6 @@ jobs: if: matrix.config.artifact == 'linux-riscv64' run: | set -e - # node 24 (npm 11) is required: node 20's npm 10.8.2 trips "set.delete - # is not a function" during npm ci on riscv64, and several devDeps need - # node >= 24.10. The built binary still targets node 20 N-API. - # ubuntu-24.04-riscv ships node 24, so prefer the preinstalled one and - # only fall back to the unofficial build when it is missing or too old. - # (actions/setup-node can't help here: actions/node-versions has no - # riscv64 entries in its versions manifest.) need=24.10.0 if command -v node >/dev/null 2>&1; then cur="$(node -p 'process.versions.node')" From 7958a860f17e91ac1f8d1de51d35cbf5012a6a10 Mon Sep 17 00:00:00 2001 From: Bruno Verachten Date: Fri, 19 Jun 2026 20:59:10 +0200 Subject: [PATCH 17/17] ci: install retry on the riscv64 runner retry --times=3 npm ci needs the retry CLI, which is not preinstalled on ubuntu-24.04-riscv. Without it the step exits 127 (command not found). Add the retry package to the riscv64 dependency install. Signed-off-by: Bruno Verachten --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b28ca504..58550882 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -162,7 +162,7 @@ jobs: if: matrix.config.name == 'Ubuntu riscv64' run: | sudo apt-get update - sudo apt-get install -y cmake build-essential ninja-build libtbb-dev gcc-14 g++-14 + sudo apt-get install -y cmake build-essential ninja-build libtbb-dev gcc-14 g++-14 retry sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 cmake --version