From 40aef0deb51a4a6dd6055e088ed2bd7e8f045cb8 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Thu, 13 Aug 2026 08:45:58 +0700 Subject: [PATCH] fix(sandbox): run the guest on Node 24 and probe /path in the e2es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guest ran the server bundle on Node 22, whose node:sqlite lacks StatementSync.setReturnArrays — the bundle is built against Electron's Node 24, so every DB-touching route died with UnknownError while /global/health kept passing, which is exactly what the health-only e2e checked. Track the Electron Node major (24) in the image, and make both e2e drivers also probe /path, the cheapest route that boots the instance store. Found via the first in-app run: the sandbox reached Ready but the project picker listed nothing; reproduced in the guest rootfs under Docker, where the server log carried the TypeError. Co-Authored-By: Claude Fable 5 --- packages/codev-sandbox/image/Dockerfile | 6 +++++- packages/codev-sandbox/script/e2e-vz.ts | 14 +++++++++++++- packages/codev-sandbox/script/e2e-wsl.ts | 14 +++++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/codev-sandbox/image/Dockerfile b/packages/codev-sandbox/image/Dockerfile index e04272ec69..88f01ed900 100644 --- a/packages/codev-sandbox/image/Dockerfile +++ b/packages/codev-sandbox/image/Dockerfile @@ -13,7 +13,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential pkg-config \ && rm -rf /var/lib/apt/lists/* ARG TARGETARCH -ARG NODE_VERSION=v22.15.0 +# Track the Node major Electron embeds (Electron 42 = Node 24): the guest runs +# the same server bundle as the desktop sidecar, and the bundle uses node:sqlite +# APIs (StatementSync.setReturnArrays) that Node 22 does not have — under 22 +# every DB-touching route fails while /global/health still passes. +ARG NODE_VERSION=v24.19.0 RUN set -eux; \ case "$TARGETARCH" in amd64) NODE_ARCH=x64 ;; arm64) NODE_ARCH=arm64 ;; *) echo "unsupported arch: $TARGETARCH" >&2; exit 1 ;; esac; \ curl -fsSL "https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz" \ diff --git a/packages/codev-sandbox/script/e2e-vz.ts b/packages/codev-sandbox/script/e2e-vz.ts index db155cf9a6..41b2740fac 100644 --- a/packages/codev-sandbox/script/e2e-vz.ts +++ b/packages/codev-sandbox/script/e2e-vz.ts @@ -58,6 +58,18 @@ console.log(`server: ${sandbox.url} (user ${sandbox.username})`) const healthy = await checkSandboxHealth(sandbox.url, { username: sandbox.username ?? "", password: sandbox.password }) console.log(`health: ${healthy ? "OK" : "FAILED"}`) +// /global/health never touches the instance store; /path boots it, so this is +// the probe that catches a guest runtime the bundle cannot actually run on +// (e.g. a node:sqlite too old for the bundle's prepared statements). +const pathsOk = await fetch(`${sandbox.url}/path`, { + headers: { + authorization: `Basic ${Buffer.from(`${sandbox.username ?? ""}:${sandbox.password}`).toString("base64")}`, + }, +}) + .then((response) => response.ok) + .catch(() => false) +console.log(`path api: ${pathsOk ? "OK" : "FAILED"}`) + if (hold) { console.log("holding the VM up; Ctrl-C to stop") await new Promise(() => {}) @@ -69,7 +81,7 @@ await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 10_000) await provider.destroy(config) server.stop(true) await rm(work, { recursive: true, force: true }) -if (!healthy) process.exit(1) +if (!healthy || !pathsOk) process.exit(1) console.log("e2e OK") process.exit(0) diff --git a/packages/codev-sandbox/script/e2e-wsl.ts b/packages/codev-sandbox/script/e2e-wsl.ts index 18b77985b2..18ebe611c2 100644 --- a/packages/codev-sandbox/script/e2e-wsl.ts +++ b/packages/codev-sandbox/script/e2e-wsl.ts @@ -74,6 +74,18 @@ console.log(`server: ${sandbox.url} (user ${sandbox.username})`) const healthy = await checkSandboxHealth(sandbox.url, { username: sandbox.username ?? "", password: sandbox.password }) console.log(`health: ${healthy ? "OK" : "FAILED"}`) +// /global/health never touches the instance store; /path boots it, so this is +// the probe that catches a guest runtime the bundle cannot actually run on +// (e.g. a node:sqlite too old for the bundle's prepared statements). +const pathsOk = await fetch(`${sandbox.url}/path`, { + headers: { + authorization: `Basic ${Buffer.from(`${sandbox.username ?? ""}:${sandbox.password}`).toString("base64")}`, + }, +}) + .then((response) => response.ok) + .catch(() => false) +console.log(`path api: ${pathsOk ? "OK" : "FAILED"}`) + if (hold) { console.log("holding the distribution up; Ctrl-C to stop") await new Promise(() => {}) @@ -85,7 +97,7 @@ await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, 10_000) if (keep) console.log(`keeping the distribution; remove it with: wsl --unregister codev-${config.name}`) if (!keep) await provider.destroy(config) await cleanup() -if (!healthy) process.exit(1) +if (!healthy || !pathsOk) process.exit(1) console.log("e2e OK") process.exit(0)