Skip to content

Drop targeted placement to test isolate distribution (#1693) #4757

Drop targeted placement to test isolate distribution (#1693)

Drop targeted placement to test isolate distribution (#1693) #4757

Workflow file for this run

name: CI
on:
pull_request:
push:
branches:
- main
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
changes:
name: Changed paths
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 10
outputs:
desktop_smoke: ${{ github.event_name != 'pull_request' || steps.filter.outputs.desktop_smoke == 'true' }}
selfhost_docker_smoke: ${{ github.event_name != 'pull_request' || steps.filter.outputs.selfhost_docker_smoke == 'true' }}
steps:
- uses: actions/checkout@v4
- name: Filter changed paths
id: filter
if: github.event_name == 'pull_request'
uses: dorny/paths-filter@v3
with:
filters: |
desktop_smoke:
- ".github/workflows/**"
- "bun.lock"
- "package.json"
- "turbo.json"
- "apps/desktop/**"
- "apps/local/**"
- "apps/cli/**"
- "packages/app/**"
- "packages/core/**"
- "packages/hosts/mcp/**"
- "packages/kernel/runtime-quickjs/**"
- "packages/plugins/**"
- "packages/react/**"
selfhost_docker_smoke:
- ".github/workflows/**"
- ".dockerignore"
- "bun.lock"
- "package.json"
- "turbo.json"
- "apps/host-selfhost/**"
- "packages/app/**"
- "packages/core/**"
- "packages/hosts/mcp/**"
- "packages/kernel/runtime-quickjs/**"
- "packages/plugins/**"
- "packages/react/**"
format:
name: Format
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
- run: bun install --frozen-lockfile --ignore-scripts
- run: bun run format:check
lint:
name: Lint
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
- run: bun install --frozen-lockfile --ignore-scripts
- run: bun run lint
typecheck:
name: Typecheck
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
env:
TURBO_API: ${{ vars.TURBO_API }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
# No prebuilt better-sqlite3 binary matches this runner, so `bun install`
# builds it from source via node-gyp, whose undici needs Node 22.10+
# (webidl.markAsUncloneable). Pin the same Node 24 runtime used by release.
- uses: actions/setup-node@v4
with:
node-version: 24
- run: bun install --frozen-lockfile
- run: bun run typecheck
# The committed routeTree.gen.ts files are generated but checked in, and
# they carry @ts-nocheck — so the typecheck above structurally CANNOT see
# a route that the contract declares and the tree lacks. That drift has
# already shipped a live 404 once. Regenerate and diff instead.
- run: bun run check:routes
test:
name: Test
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 15
# Run eight independent packages at once and cap each Vitest process at two
# workers. This uses all 16 vCPUs without every nested runner seeing the
# whole machine and oversubscribing it.
env:
TURBO_API: ${{ vars.TURBO_API }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
TURBO_TEST_CONCURRENCY: 8
VITEST_MAX_WORKERS: 2
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
# apps/cloud's test script invokes `node` directly; undici 8.x (pulled
# in by @cloudflare/vitest-pool-workers) calls webidl.markAsUncloneable
# which only exists in Node 22.10+. Pin the Node 24 CI runtime.
- uses: actions/setup-node@v4
with:
node-version: 24
- run: bun install --frozen-lockfile
# Fail loudly if a load-bearing patched dependency (e.g. the agents MCP
# transport hang fix) resolved to a stale, unpatched dist. Without this,
# a bun cache edge case can leave the patch unapplied while everything
# still builds and tests green, and prod ships the pre-fix transport with
# no signal. This is just a few file reads plus greps, so ~zero cost.
- run: bun run check:patches
- run: bun run --cwd e2e test:ci-shard
- run: bun run test
e2e:
name: E2E (${{ matrix.target }}${{ matrix['shard-name'] && format(' {0}', matrix['shard-name']) || '' }})
strategy:
fail-fast: false
matrix:
include:
# The planner assigns every file exactly once using recorded slow-file
# durations plus a conservative weight for new tests. The cloud DB's
# connection teardown and concurrent socket protocol have dedicated
# regression tests; these shards balance wall clock, not hide retries.
- { target: cloud, shard-index: 1, shard-name: 1of16 }
- { target: cloud, shard-index: 2, shard-name: 2of16 }
- { target: cloud, shard-index: 3, shard-name: 3of16 }
- { target: cloud, shard-index: 4, shard-name: 4of16 }
- { target: cloud, shard-index: 5, shard-name: 5of16 }
- { target: cloud, shard-index: 6, shard-name: 6of16 }
- { target: cloud, shard-index: 7, shard-name: 7of16 }
- { target: cloud, shard-index: 8, shard-name: 8of16 }
- { target: cloud, shard-index: 9, shard-name: 9of16 }
- { target: cloud, shard-index: 10, shard-name: 10of16 }
- { target: cloud, shard-index: 11, shard-name: 11of16 }
- { target: cloud, shard-index: 12, shard-name: 12of16 }
- { target: cloud, shard-index: 13, shard-name: 13of16 }
- { target: cloud, shard-index: 14, shard-name: 14of16 }
- { target: cloud, shard-index: 15, shard-name: 15of16 }
- { target: cloud, shard-index: 16, shard-name: 16of16 }
- { target: selfhost, shard-index: 1, shard-name: 1of10 }
- { target: selfhost, shard-index: 2, shard-name: 2of10 }
- { target: selfhost, shard-index: 3, shard-name: 3of10 }
- { target: selfhost, shard-index: 4, shard-name: 4of10 }
- { target: selfhost, shard-index: 5, shard-name: 5of10 }
- { target: selfhost, shard-index: 6, shard-name: 6of10 }
- { target: selfhost, shard-index: 7, shard-name: 7of10 }
- { target: selfhost, shard-index: 8, shard-name: 8of10 }
- { target: selfhost, shard-index: 9, shard-name: 9of10 }
- { target: selfhost, shard-index: 10, shard-name: 10of10 }
# Local files own their server, browser and data directory. Separate
# runners preserve that isolation while removing its 69-second serial
# lane from the two-minute critical path.
- { target: local, shard-index: 1, shard-name: 1of2 }
- { target: local, shard-index: 2, shard-name: 2of2 }
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
# The dev stacks spawn Node sidecars (vite/workerd tooling); pin the
# same Node 24 runtime the release and publish workflows use.
- uses: actions/setup-node@v4
with:
node-version: 24
- run: bun install --frozen-lockfile
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-1.60.0
restore-keys: |
${{ runner.os }}-playwright-
# Install from e2e so bunx resolves ITS pinned playwright (the version
# the tests run against) rather than floating to the latest.
# Blacksmith's Ubuntu image carries the official GitHub runner system
# dependencies. Restore the pinned browser binaries without apt-updating
# every matrix machine.
- name: Install Playwright Chromium
run: bunx playwright install chromium chromium-headless-shell
working-directory: e2e
# Each target either boots its own shared dev server or lets each file own
# its server. Ports and data paths are hermetic in both cases.
# Do not retry scenarios: retries hide flakes and multiply slow timeout
# failures. The fixtures and process lifecycle are deterministic enough
# that the first result is the result.
- name: Run cloud shard
if: matrix.target == 'cloud'
env:
MCP_SESSION_TIMEOUT_MS: "3000"
MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS: "6000"
run: bun scripts/run-ci-shard.ts cloud ${{ matrix['shard-index'] }}
working-directory: e2e
- name: Run scenarios
if: matrix.target != 'cloud'
run: bun scripts/run-ci-shard.ts ${{ matrix.target }} ${{ matrix['shard-index'] }}
working-directory: e2e
# Failed runs keep their trace.zip / session.mp4 / step screenshots in
# runs/<target>/<slug>/ — surface them instead of a bare red X.
- name: Upload run artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-runs-${{ matrix.target }}${{ matrix['shard-name'] && format('-{0}', matrix['shard-name']) || '' }}
path: e2e/runs/
retention-days: 7
desktop-smoke:
name: Desktop smoke build
needs: changes
if: needs.changes.outputs.desktop_smoke == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
- name: Cache Bun package cache
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-1.3.11-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-1.3.11-
# No prebuilt better-sqlite3 binary matches this runner, so `bun install`
# builds it from source via node-gyp, whose undici needs Node 22.10+
# (webidl.markAsUncloneable). Pin the same Node 24 CI runtime.
- uses: actions/setup-node@v4
with:
node-version: 24
- run: bun install --frozen-lockfile
- name: Build web app
run: bun run --filter @executor-js/local build
- name: Build bundled executor
env:
BUN_TARGET: bun-linux-x64
run: bun ./scripts/build-sidecar.ts
working-directory: apps/desktop
# Run the compiled sidecar in a clean container where the build
# workspace does not exist. bun --compile bakes build-machine paths
# into __dirname for runtime-loaded assets; running the binary on the
# build machine hides that entire failure class (this exact gap
# shipped a broken 1Password SDK: its sdk-core wasm was read from the
# CI runner's node_modules path at runtime, ENOENT on every user
# machine). Pass condition: the 1Password endpoint reaches real
# credential validation, not a module-load or empty-namespace error.
- name: Run sidecar outside the workspace
working-directory: apps/desktop
run: |
docker run --rm -d --name sidecar-smoke -p 45841:45841 -e HOME=/tmp \
-v "$PWD/resources/executor:/opt/executor:ro" \
debian:bookworm-slim /opt/executor/executor daemon run --foreground \
--port 45841 --hostname 0.0.0.0 --auth-token=ci-smoke
for i in $(seq 1 60); do
code=$(curl -s -o /dev/null -w '%{http_code}' -H "Authorization: Bearer ci-smoke" http://127.0.0.1:45841/api/onepassword/vaults || true)
[ "$code" != "000" ] && break
docker ps -q -f name=sidecar-smoke | grep -q . || { docker logs sidecar-smoke; exit 1; }
sleep 1
done
body=$(curl -s -H "Authorization: Bearer ci-smoke" \
"http://127.0.0.1:45841/api/onepassword/vaults?authKind=service-account&account=ops_ci_fake_token")
docker stop sidecar-smoke
echo "$body"
if echo "$body" | grep -qE "sdk module load|ENOENT|is not a function|not a constructor"; then
echo "::error::1Password SDK failed to load inside the compiled binary (baked build path or missing sibling asset)"
exit 1
fi
if ! echo "$body" | grep -q "invalid service account token"; then
echo "::error::sidecar did not reach 1Password credential validation; unexpected response above"
exit 1
fi
- name: Build Electron main/preload/renderer
run: bunx --bun electron-vite build
working-directory: apps/desktop
selfhost-docker-smoke:
name: Self-host Docker image
needs: changes
if: needs.changes.outputs.selfhost_docker_smoke == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Setup Blacksmith Builder
uses: useblacksmith/setup-docker-builder@v1
- name: Build self-host image
uses: useblacksmith/build-push-action@v2
with:
context: .
file: apps/host-selfhost/Dockerfile
push: false
load: true
tags: executor-selfhost:ci
# Building alone doesn't catch startup crashes (e.g. a runtime-resolved
# package missing from the packaged runtime), so boot the container and
# require a healthy /api/health before calling the image good.
- name: Boot image and check health
shell: bash
run: |
set -euo pipefail
docker run -d --name selfhost-smoke -p 4788:4788 \
-e BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \
-e EXECUTOR_BOOTSTRAP_ADMIN_EMAIL=smoke@example.com \
-e EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD="$(openssl rand -hex 16)" \
-e EXECUTOR_WEB_BASE_URL=http://localhost:4788 \
executor-selfhost:ci
for i in $(seq 1 30); do
if [ "$(docker inspect -f '{{.State.Running}}' selfhost-smoke)" != "true" ]; then
echo "container exited during startup"
docker logs selfhost-smoke
exit 1
fi
if curl -fsS http://localhost:4788/api/health >/dev/null 2>&1; then
echo "healthy after ${i}s"
docker logs selfhost-smoke
exit 0
fi
sleep 1
done
echo "health check never passed"
docker logs selfhost-smoke
exit 1