diff --git a/.github/actions/setup-node-env/action.yml b/.github/actions/setup-node-env/action.yml index 221c0b367e85..baa47c6173e6 100644 --- a/.github/actions/setup-node-env/action.yml +++ b/.github/actions/setup-node-env/action.yml @@ -321,7 +321,7 @@ runs: shell: bash run: | set -euo pipefail - npm install -g bun@1.3.14 + npm install -g bun@1.4.0 - name: Runtime versions shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9952575ad4f8..efc732e4b3de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1346,7 +1346,7 @@ jobs: uses: ./.ci-harness/.github/actions/setup-node-env with: cache-mode: ${{ needs.preflight.outputs.cache_mode }} - install-bun: "false" + install-bun: "true" node-compile-cache: "true" node-compile-cache-scope: "build" # Same-repo Blacksmith runs restore the dependency cache published by @@ -1428,6 +1428,11 @@ jobs: - name: Smoke test CLI launcher status json run: node openclaw.mjs status --json --timeout 1 + - name: Smoke test built CLI with Bun + run: | + bun openclaw.mjs --help + bun openclaw.mjs status --json --timeout 1 + # Doctor index proof, singleton smoke, and startup-memory check are # independent dist readers; Blacksmith runners have the cores to overlap # them, while hosted 4-core runners keep the serial order so the RSS diff --git a/.github/workflows/install-smoke-reusable.yml b/.github/workflows/install-smoke-reusable.yml index 1e6bd1eabf78..041d5a3e51cf 100644 --- a/.github/workflows/install-smoke-reusable.yml +++ b/.github/workflows/install-smoke-reusable.yml @@ -1449,7 +1449,7 @@ jobs: --source-archive-sha256 "$SOURCE_ARCHIVE_SHA256" - name: Install Bun for global smoke - run: npm install -g bun@1.3.14 + run: npm install -g bun@1.4.0 - name: Run Bun global install candidate-payload smoke working-directory: .release-harness diff --git a/Dockerfile b/Dockerfile index a3552f1855b7..a566b0668cd3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="docker.io/library/node:24-bookworm-slim@s ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST="sha256:3638d9a6fe4030bd716be989438248074489337ba3275657f93595428be4fc03" # Keep in sync with .github/actions/setup-node-env/action.yml bun-version. # To update: docker buildx imagetools inspect docker.io/oven/bun: and use the manifest-list digest. -ARG OPENCLAW_BUN_IMAGE="docker.io/oven/bun:1.3.14@sha256:e10577f0db68676a7024391c6e5cb4b879ebd17188ab750cf10024a6d700e5c4" +ARG OPENCLAW_BUN_IMAGE="docker.io/oven/bun:1.4.0@sha256:5ff609364c049b54eb0ff560ec96319729a972078ef2c755d758f0c6ef89c2d6" # Base images are pinned to SHA256 digests for reproducible builds. # Dependabot refreshes these blessed digests; release builds consume the diff --git a/scripts/crabbox-wrapper.mts b/scripts/crabbox-wrapper.mts index 46c62f0faba9..8bb45651098f 100755 --- a/scripts/crabbox-wrapper.mts +++ b/scripts/crabbox-wrapper.mts @@ -275,7 +275,7 @@ const jsRuntimeEntrypoints = new Set([ ]); const awsMacosCorepackEntrypoints = new Set(["pnpm", "yarn", "corepack"]); const awsMacosBunEntrypoints = new Set(["bun", "bunx"]); -const awsMacosBunVersion = "1.3.14"; +const awsMacosBunVersion = "1.4.0"; const awsMacosSwiftEntrypoints = new Set(["swift", "xcodebuild"]); const awsMacosSwiftScriptTargets = new Set([ "mac:package", diff --git a/scripts/e2e/bun-global-install-smoke.sh b/scripts/e2e/bun-global-install-smoke.sh index 490a3e13b9ad..2d0dc52cc549 100755 --- a/scripts/e2e/bun-global-install-smoke.sh +++ b/scripts/e2e/bun-global-install-smoke.sh @@ -147,8 +147,8 @@ main() { export NO_COLOR=1 mkdir -p "$HOME" "$BUN_INSTALL/bin" "$BUN_INSTALL/install/global" "$XDG_CACHE_HOME" export PATH="$BUN_INSTALL/bin:$(dirname "$(command -v node)"):$PATH" - # Release publishes @openclaw/ai first. Bun 1.3.14 ignores bundled deps in - # local tarballs, so resolve that one package from the exact candidate bytes. + # Release publishes @openclaw/ai first. Pin the local tarball install to + # exact candidate bytes instead of allowing public-registry resolution. node --input-type=module - \ "$BUN_INSTALL/install/global/package.json" \ "$AI_PACKAGE_TGZ" <<'NODE' diff --git a/scripts/e2e/docker-package-install.sh b/scripts/e2e/docker-package-install.sh index 9b03bbb7e10a..acfb4c2997c3 100755 --- a/scripts/e2e/docker-package-install.sh +++ b/scripts/e2e/docker-package-install.sh @@ -86,7 +86,7 @@ DOCKER_COMMAND_TIMEOUT="$DOCKER_RUN_TIMEOUT" docker_e2e_docker_run_cmd run -d \ "$IMAGE_NAME" \ bash -lc ' set -euo pipefail - npm install -g --prefix /tmp/bun-runtime bun@1.3.14 --no-fund --no-audit + npm install -g --prefix /tmp/bun-runtime bun@1.4.0 --no-fund --no-audit cd /repo BUN_BIN=/tmp/bun-runtime/bin/bun \ OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD=0 \ diff --git a/src/cli/gateway-backed-exit.process.test.ts b/src/cli/gateway-backed-exit.process.test.ts index d1a2faf8b887..6800bc459110 100644 --- a/src/cli/gateway-backed-exit.process.test.ts +++ b/src/cli/gateway-backed-exit.process.test.ts @@ -148,6 +148,7 @@ async function runIsolatedGatewayCli(params: { stateDir: string; configPath: string; env?: NodeJS.ProcessEnv; + timeoutMs?: number; }): Promise<{ code: number | null; signal: NodeJS.Signals | null; @@ -185,7 +186,7 @@ async function runIsolatedGatewayCli(params: { }, encoding: "utf8", killSignal: "SIGKILL", - timeout: 20_000, + timeout: params.timeoutMs ?? 20_000, }, ); return { code: 0, signal: null, stdout: result.stdout, stderr: result.stderr }; @@ -235,6 +236,9 @@ describe("gateway-backed CLI process exit", () => { root, stateDir, configPath, + // The first source child cold-loads the CLI graph and can contend with + // neighboring CI shards before it reaches the Gateway turn. + timeoutMs: 45_000, }); expect(result, result.stderr).toMatchObject({ code: exitCode, signal: null, stderr: "" }); @@ -244,7 +248,7 @@ describe("gateway-backed CLI process exit", () => { result: { payloads: [{ text }] }, }); }, - 30_000, + 60_000, ); it.each([ diff --git a/src/dockerfile.test.ts b/src/dockerfile.test.ts index d275aeb2bb57..3056b93cca63 100644 --- a/src/dockerfile.test.ts +++ b/src/dockerfile.test.ts @@ -85,7 +85,7 @@ describe("Dockerfile", () => { 'ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="docker.io/library/node:24-bookworm-slim@sha256:3638d9a6fe4030bd716be989438248074489337ba3275657f93595428be4fc03"', ); expect(dockerfile).toContain( - 'ARG OPENCLAW_BUN_IMAGE="docker.io/oven/bun:1.3.14@sha256:e10577f0db68676a7024391c6e5cb4b879ebd17188ab750cf10024a6d700e5c4"', + 'ARG OPENCLAW_BUN_IMAGE="docker.io/oven/bun:1.4.0@sha256:5ff609364c049b54eb0ff560ec96319729a972078ef2c755d758f0c6ef89c2d6"', ); expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS workspace-deps"); expect(dockerfile).toContain("FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS build"); diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index 6de2937e33c3..4f76e8503579 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -7405,13 +7405,26 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}" expect(realGatewayRunContract).not.toContain("--testTimeout"); }); - it("does not rebuild Control UI after build:ci-artifacts", () => { + it("builds artifacts once and smoke-tests the built CLI with Node and Bun", () => { const workflow = readCiWorkflow(); const buildArtifactSteps = workflow.jobs["build-artifacts"].steps; + const setupStep = buildArtifactSteps.find( + (step: WorkflowStep) => step.name === "Setup Node environment", + ); const buildDistStep = buildArtifactSteps.find( (step: WorkflowStep) => step.name === "Build dist", ); + const nodeHelpSmoke = buildArtifactSteps.find( + (step: WorkflowStep) => step.name === "Smoke test CLI launcher help", + ); + const nodeStatusSmoke = buildArtifactSteps.find( + (step: WorkflowStep) => step.name === "Smoke test CLI launcher status json", + ); + const bunSmoke = buildArtifactSteps.find( + (step: WorkflowStep) => step.name === "Smoke test built CLI with Bun", + ); + expect(setupStep.with["install-bun"]).toBe("true"); expect(buildDistStep.run).toBe("pnpm build:ci-artifacts"); expect(buildArtifactSteps.map((step: WorkflowStep) => step.name)).not.toContain( "Build Control UI", @@ -7419,6 +7432,10 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}" expect(buildArtifactSteps.some((step: WorkflowStep) => step.run === "pnpm ui:build")).toBe( false, ); + expect(nodeHelpSmoke.run).toBe("node openclaw.mjs --help"); + expect(nodeStatusSmoke.run).toBe("node openclaw.mjs status --json --timeout 1"); + expect(bunSmoke.run).toContain("bun openclaw.mjs --help"); + expect(bunSmoke.run).toContain("bun openclaw.mjs status --json --timeout 1"); }); it("keeps source-only Control UI locale drift advisory", () => { diff --git a/test/scripts/crabbox-wrapper.test.ts b/test/scripts/crabbox-wrapper.test.ts index 72c7b1a172f0..089fb00ff03a 100644 --- a/test/scripts/crabbox-wrapper.test.ts +++ b/test/scripts/crabbox-wrapper.test.ts @@ -2575,7 +2575,7 @@ describe("scripts/crabbox-wrapper", () => { expect(output.args).toContain("--shell"); expect(result.stderr).toContain("Node/Corepack/pnpm/Bun"); expect(remoteCommand).toContain("openclaw_crabbox_bootstrap_macos_js"); - expect(remoteCommand).toContain("bun_version=1.3.14"); + expect(remoteCommand).toContain("bun_version=1.4.0"); expect(remoteCommand).toContain('bun_root="$tool_root/bun-v${bun_version}"'); expect(remoteCommand).toContain( 'npm install --global --prefix "$bun_root" --fetch-timeout=120000 --fetch-retries=2 --fetch-retry-mintimeout=2000 --fetch-retry-maxtimeout=15000 "bun@${bun_version}"', @@ -2871,7 +2871,7 @@ describe("scripts/crabbox-wrapper", () => { it("bootstraps Bun for AWS macOS script-stdin bun shebangs", () => { const script = ["#!/usr/bin/env bun", "console.log(Bun.version);"].join("\n"); const { output } = runSuccessfulMacosScript(script); - expect(output.scriptContent).toContain("bun_version=1.3.14"); + expect(output.scriptContent).toContain("bun_version=1.4.0"); expect(output.scriptContent).toContain( 'npm install --global --prefix "$bun_root" --fetch-timeout=120000 --fetch-retries=2 --fetch-retry-mintimeout=2000 --fetch-retry-maxtimeout=15000 "bun@${bun_version}"', ); diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index 5e2789422d49..a3d125b48af7 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -4520,7 +4520,7 @@ source "$ROOT_DIR/scripts/lib/docker-e2e-logs.sh" "npm install -g --prefix /tmp/openclaw-proof", "corepack prepare pnpm@11.22.0 --activate", "pnpm add --global --allow-build=openclaw", - "bun@1.3.14", + "bun@1.4.0", 'test "$(command -v openclaw)" = "/tmp/openclaw-proof/bin/openclaw"', 'test "$(command -v openclaw)" = "$PNPM_HOME/openclaw"', "OPENCLAW_BUN_GLOBAL_SMOKE_PROOF_PATH", diff --git a/test/scripts/install-smoke-no-push-workflow.test.ts b/test/scripts/install-smoke-no-push-workflow.test.ts index 0725434b3f08..a242ef2b0c5a 100644 --- a/test/scripts/install-smoke-no-push-workflow.test.ts +++ b/test/scripts/install-smoke-no-push-workflow.test.ts @@ -543,7 +543,7 @@ describe("install smoke no-push root image transport", () => { expect(bunVerify.run).toContain("install-smoke-candidate-payload.mts verify"); expect(bunVerify.run).toContain('--run-id "$PRODUCER_RUN_ID"'); expect(bunVerify.run).toContain('--run-attempt "$PRODUCER_RUN_ATTEMPT"'); - expect(step(bunConsumer, "Install Bun for global smoke").run).toBe("npm install -g bun@1.3.14"); + expect(step(bunConsumer, "Install Bun for global smoke").run).toBe("npm install -g bun@1.4.0"); expect(step(bunConsumer, "Run Bun global install candidate-payload smoke")).toMatchObject({ "working-directory": ".release-harness", env: { diff --git a/test/scripts/test-install-sh-docker.test.ts b/test/scripts/test-install-sh-docker.test.ts index 462795f2e5b9..4bcbc09895d8 100644 --- a/test/scripts/test-install-sh-docker.test.ts +++ b/test/scripts/test-install-sh-docker.test.ts @@ -2171,7 +2171,7 @@ chmod +x "$BUN_INSTALL/bin/openclaw" expect(workflow).toContain("bun_global_install_smoke:"); expect(workflow).toContain("Setup trusted release harness for Bun smoke"); expect(workflow).toContain("uses: ./.release-harness/.github/actions/setup-release-harness"); - expect(workflow).toContain("npm install -g bun@1.3.14"); + expect(workflow).toContain("npm install -g bun@1.4.0"); expect(workflow).toContain('install-bun: "false"'); expect(workflow).toContain("Run Bun global install candidate-payload smoke"); expect(workflow).toContain("working-directory: .release-harness");