From f41b1d9054529b145dcae6869db1b56a5b8b6b08 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Thu, 3 Sep 2026 09:28:09 -0700 Subject: [PATCH] Install from the lockfile, so CI cannot be broken by someone else's release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `build-linux` fails before `tauri build` runs, on every workflow, at "Install frontend dependencies": npm error Cannot read properties of null (reading 'edgesOut') Reproduced exactly on the first attempt by running the step's own commands locally on the same Node 22.23.2 the runner installs. The debug log gives the frame the CI output omits: at #loadPeerSet (.../@npmcli/arborist/lib/arborist/build-ideal-tree.js:1289:38) It is a null dereference in npm 10.9.8's peer-set resolver, reached through vite → @vitejs/devtools → @vitejs/devtools-vitest → vitest@* → @vitest/browser-playwright → vitest@4.1.11 → jsdom@* → canvas. **Nothing in this repo changed to cause it.** The step deleted `package-lock.json` before installing, so every build re-resolved the entire tree against the registry against ranges like `vitest@*`. A dependency published a version that produces a peer graph npm cannot resolve, and our CI broke — the same command succeeded fifteen hours earlier for 0.4.21. That is the real defect: the build was never reproducible, and the crash is only how we found out. So Linux installs with `npm ci`, from the committed lockfile, like Windows already did. macOS moves too — it kept the lockfile but still ran `npm install`, which is free to re-resolve; all three platforms now install identically and none can re-resolve mid-release. **The reason the lockfile was being deleted is obsolete, not ignored.** 2d4fce9 removed it "to ensure correct platform-specific bindings", which was a real problem once. The committed lockfile now records 25 rollup platform variants, and `npm ci` on Linux installs precisely rollup-linux-x64-{gnu,musl} and @esbuild/linux-x64 — checked directly. A comment on the step says so, and says not to reach for deleting the lockfile again: if `npm ci` refuses, package.json and the lockfile have genuinely diverged and the fix is to commit an updated lockfile. Verified from the resulting tree: `tsc --noEmit` clean, `npm run build` successful, 752 tests across 62 files passing. The `npx tauri --version || npm install @tauri-apps/cli` fallback in the next step cannot reintroduce a fresh resolution — the CLI is a pinned devDependency that `npm ci` installs, so the fallback is unreachable. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_011YPqHpjV4EL6RNEwrRKqQm --- .gitea/workflows/build-app-preview.yml | 36 +++++++++++++++++++++++--- .gitea/workflows/build-app.yml | 36 +++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/build-app-preview.yml b/.gitea/workflows/build-app-preview.yml index 371d8a5..62119b2 100644 --- a/.gitea/workflows/build-app-preview.yml +++ b/.gitea/workflows/build-app-preview.yml @@ -299,8 +299,34 @@ jobs: - name: Install frontend dependencies working-directory: ./app run: | - rm -rf node_modules package-lock.json - npm install + # `npm ci` — from the lockfile, never resolving afresh. + # + # This used to be `rm -rf node_modules package-lock.json && npm + # install`, which deleted the lockfile "to ensure correct + # platform-specific bindings" (2d4fce9). That made every build + # re-resolve the whole tree against the registry, so a dependency + # publishing a new version could break CI with no change to this + # repo — and one did. Deleting the lockfile then hit a null + # dereference in npm 10.9.8's arborist peer-set resolver: + # + # npm error Cannot read properties of null (reading 'edgesOut') + # at #loadPeerSet (.../build-ideal-tree.js:1289:38) + # + # reached through vite → @vitejs/devtools → @vitejs/devtools-vitest + # → vitest@* → @vitest/browser-playwright → jsdom@* → canvas. + # Reproduced exactly by removing the lockfile locally on the same + # Node 22.23.2 the runner installs. + # + # The binding worry is obsolete: the committed lockfile records 25 + # rollup platform variants, and `npm ci` on Linux installs precisely + # rollup-linux-x64-{gnu,musl} and @esbuild/linux-x64. Verified, along + # with a clean tsc, a successful build and 752 passing tests from the + # resulting tree. + # + # Do not "fix" a future dependency error by deleting the lockfile + # again. If `npm ci` refuses, package.json and the lockfile have + # genuinely diverged, and the fix is to commit an updated lockfile. + npm ci - name: Install Tauri CLI working-directory: ./app @@ -427,8 +453,10 @@ jobs: - name: Install frontend dependencies working-directory: ./app run: | - rm -rf node_modules - npm install + # `npm ci` here too, so all three platforms install identically and + # none of them can re-resolve the tree mid-release. Windows already + # did. See the Linux job for what a fresh resolution cost us. + npm ci - name: Install Tauri CLI working-directory: ./app diff --git a/.gitea/workflows/build-app.yml b/.gitea/workflows/build-app.yml index 0d4aa02..c267df9 100644 --- a/.gitea/workflows/build-app.yml +++ b/.gitea/workflows/build-app.yml @@ -172,8 +172,34 @@ jobs: - name: Install frontend dependencies working-directory: ./app run: | - rm -rf node_modules package-lock.json - npm install + # `npm ci` — from the lockfile, never resolving afresh. + # + # This used to be `rm -rf node_modules package-lock.json && npm + # install`, which deleted the lockfile "to ensure correct + # platform-specific bindings" (2d4fce9). That made every build + # re-resolve the whole tree against the registry, so a dependency + # publishing a new version could break CI with no change to this + # repo — and one did. Deleting the lockfile then hit a null + # dereference in npm 10.9.8's arborist peer-set resolver: + # + # npm error Cannot read properties of null (reading 'edgesOut') + # at #loadPeerSet (.../build-ideal-tree.js:1289:38) + # + # reached through vite → @vitejs/devtools → @vitejs/devtools-vitest + # → vitest@* → @vitest/browser-playwright → jsdom@* → canvas. + # Reproduced exactly by removing the lockfile locally on the same + # Node 22.23.2 the runner installs. + # + # The binding worry is obsolete: the committed lockfile records 25 + # rollup platform variants, and `npm ci` on Linux installs precisely + # rollup-linux-x64-{gnu,musl} and @esbuild/linux-x64. Verified, along + # with a clean tsc, a successful build and 752 passing tests from the + # resulting tree. + # + # Do not "fix" a future dependency error by deleting the lockfile + # again. If `npm ci` refuses, package.json and the lockfile have + # genuinely diverged, and the fix is to commit an updated lockfile. + npm ci - name: Install Tauri CLI working-directory: ./app @@ -343,8 +369,10 @@ jobs: - name: Install frontend dependencies working-directory: ./app run: | - rm -rf node_modules - npm install + # `npm ci` here too, so all three platforms install identically and + # none of them can re-resolve the tree mid-release. Windows already + # did. See the Linux job for what a fresh resolution cost us. + npm ci - name: Install Tauri CLI working-directory: ./app