Skip to content

Hold newly promoted tools off the wire until a cache-safe boundary #2315

Hold newly promoted tools off the wire until a cache-safe boundary

Hold newly promoted tools off the wire until a cache-safe boundary #2315

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: ${{ github.event_name != 'push' }}
jobs:
# oxfmt, oxlint, and typecheck share one runner: one checkout and one
# install instead of three of each. Dummy job names prettier and eslint
# stay for protect-main.
static-analysis:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
# Exact-key-only: a restore-keys prefix of bun- would hydrate
# node_modules from a different lockfile. bun install then has to
# reconcile a stale tree; missing that step leaves wrong deps.
key: bun-${{ hashFiles('bun.lock') }}
- name: Install dependencies
run: bun install --frozen-lockfile
- name: oxfmt
run: bunx oxfmt --check .
- name: oxlint
run: bunx oxlint .
- name: Typecheck
run: bun run typecheck
# Build runs beside the suite instead of before it: tests import ./src
# directly and never read ./dist, so serializing build ahead of test put
# build time on the critical path for no dependency reason.
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
key: bun-${{ hashFiles('bun.lock') }}
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build
run: bun run build
# The suite is sharded so the slowest slice, not the whole suite, sets the
# wall clock. The old ./src leg (387 files, ~88s local) is split into three
# path-disjoint shards measured at ~30s (src-a: tui, 146 files), ~46s
# (src-b: agent + subagent, 90 files), and ~45s (src-c: everything else in
# src, 153 files); the ./tests leg stays whole because ./evals ./scripts
# finish in ~1s and a leg of their own would be all setup overhead.
# Every shard still goes through check:projects-dir-guard: the
# guard forwards these path filters to the suite it wraps, and the union of
# the shards' filters is exactly ./src ./tests ./evals ./scripts, so the gate covers
# the same tests as before, all of them sandboxed.
test:
runs-on: ubuntu-latest
strategy:
# A red shard must not cancel the others; every result is the signal.
fail-fast: false
matrix:
shard:
- name: src-a
paths: ./src/tui
- name: src-b
paths: ./src/agent ./src/subagent
- name: src-c
paths: ./src/auth ./src/changelog ./src/config ./src/cost ./src/crash ./src/exec ./src/logging ./src/mcp ./src/perf ./src/permission ./src/plugins ./src/provider ./src/session ./src/shell ./src/telemetry ./src/tools ./src/trust ./src/upgrade ./src/util ./src/web ./src/config.test.ts ./src/context-compactor.test.ts ./src/director.test.ts ./src/inference-abort.test.ts ./src/inference-error-message.test.ts ./src/inference-gateway-error.test.ts ./src/list-dir.test.ts ./src/pricing-fetcher.test.ts ./src/pricing-metadata.test.ts ./src/profiles.test.ts ./src/prompts.test.ts ./src/renderer.test.ts ./src/settings.test.ts ./src/state.test.ts
- name: tests-evals-and-scripts
paths: ./tests ./evals ./scripts
name: test (${{ matrix.shard.name }})
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.14"
# The runner image has no ripgrep, so the grep plugin silently exercised
# its fallback walker and left the ripgrep path untested.
- name: Install ripgrep
run: sudo apt-get install -y ripgrep
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
key: bun-${{ hashFiles('bun.lock') }}
- name: Install dependencies
run: bun install --frozen-lockfile
# The same script the local `bun run check` gate runs, with the shard's
# path filters forwarded through the guard to the suite. The guard
# routes a filtered run through test:paths, which carries the same
# seeded flags as the `test` script; bun test filters are additive, so
# appending filters to `bun run test` could not narrow it. Randomized
# order catches tests that only pass in the default file order (shared
# module-level state, an unrestored global mock, a leaked env var).
# The seed stays 424242 in every shard rather than varying per shard:
# the shards already run disjoint file sets, and a fixed seed keeps
# any failure reproducible locally with the same
# `bun run test:paths <paths>`.
- name: Test
run: bun run check:projects-dir-guard ${{ matrix.shard.paths }}
# protect-main still requires the pre-restructure check names. These jobs
# exist only to publish those contexts after the real work succeeds.
prettier:
needs: static-analysis
runs-on: ubuntu-latest
steps:
- run: "true"
eslint:
needs: static-analysis
runs-on: ubuntu-latest
steps:
- run: "true"
typecheck:
needs: static-analysis
runs-on: ubuntu-latest
steps:
- run: "true"
build-and-test:
needs: [build, test]
runs-on: ubuntu-latest
steps:
- run: "true"
# The src-a/src-b/src-c matrix legs replaced the single test (src) leg, so
# this publishes that context name once all legs pass. needs: test waits for
# every matrix leg; a red leg skips this instead of greening it.
test-src:
name: test (src)
needs: test
runs-on: ubuntu-latest
steps:
- run: "true"