Support env-backed CLI server headers #3787
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
| test: | |
| name: Test | |
| runs-on: blacksmith-4vcpu-ubuntu-2404 | |
| timeout-minutes: 15 | |
| # Tuned for Blacksmith's 4 vCPU runners. | |
| 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: 4 | |
| 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 | |
| - run: bun run test | |
| e2e: | |
| name: E2E (${{ matrix.target }}${{ matrix['shard-name'] && format(' {0}', matrix['shard-name']) || '' }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # Each cloud shard boots its own fresh dev stack. On 4 vCPU runners, | |
| # four fatter shards keep the longest shard below selfhost while saving | |
| # four runner boots and four warm cache restores. | |
| - { target: cloud, shard: 1/4, shard-name: 1of4 } | |
| - { target: cloud, shard: 2/4, shard-name: 2of4 } | |
| - { target: cloud, shard: 3/4, shard-name: 3of4 } | |
| - { target: cloud, shard: 4/4, shard-name: 4of4 } | |
| - target: selfhost | |
| 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. | |
| - name: Install Playwright Chromium | |
| run: bunx playwright install --with-deps chromium chromium-headless-shell | |
| working-directory: e2e | |
| # The globalsetup boots the target's own dev server (ports are claimed | |
| # per checkout, so this is hermetic) and tears it down after the run. | |
| # --retry=2: browser scenarios can still hit isolated waitFor timeouts | |
| # (single-test waitFor timeouts, not systemic failures); a retry on the | |
| # same booted stack clears them. | |
| - name: Run cloud scenarios | |
| if: matrix.target == 'cloud' | |
| env: | |
| MCP_SESSION_TIMEOUT_MS: "3000" | |
| MCP_PAUSED_SESSION_IDLE_TIMEOUT_MS: "6000" | |
| run: bunx vitest run --project cloud --retry=2 ${{ matrix.shard && format('--shard={0}', matrix.shard) || '' }} | |
| working-directory: e2e | |
| - name: Run selfhost scenarios | |
| if: matrix.target == 'selfhost' | |
| run: bunx vitest run --project selfhost --retry=2 | |
| 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 | |
| e2e-local: | |
| name: E2E (stdio MCP) | |
| # Skipped on pull_request: the local scenario boots a real `executor web` | |
| # plus a browser and is currently flaky on PRs. Still runs on push to main. | |
| if: github.event_name != 'pull_request' | |
| 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- | |
| # The local scenarios boot a real `executor web` (which spawns a Node | |
| # sidecar) and some drive a browser, so pin Node 24 and install Chromium. | |
| - 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- | |
| # `chromium` and the new `chromium-headless-shell` ship as separate | |
| # downloads; the browser-driven scenarios launch the headless shell. | |
| # Install from e2e so bunx resolves ITS pinned playwright (the version the | |
| # tests run against) rather than floating to the latest, which would fetch | |
| # a browser build the test runtime does not look for. | |
| - name: Install Playwright Chromium | |
| run: bunx playwright install --with-deps chromium chromium-headless-shell | |
| working-directory: e2e | |
| # The `local` project is excluded from the default `test` chain (each | |
| # scenario boots its own `executor web`). Run just the stdio MCP scenario | |
| # here: it is the auto-connect / env-as-secret regression guard, and | |
| # running it alone avoids the boot-resource accumulation and the | |
| # pre-existing browser flakiness of the rest of the local suite. Expanding | |
| # to the full `local` project (bun run test:local) is a follow-up once | |
| # those are stabilized. | |
| - name: Run the stdio MCP scenario | |
| run: bunx vitest run --project local local/stdio-mcp.test.ts | |
| working-directory: e2e | |
| 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 | |
| tags: executor-selfhost:ci |