CI workflow (jackwener/OpenCLI)
The CI workflow from jackwener/OpenCLI, explained and optimized by Latchkey.
CI health: B - good
Point runs-on at Latchkey and get job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the CI workflow from the jackwener/OpenCLI repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: CI
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
schedule:
- cron: '0 8 * * 1' # Weekly Monday 08:00 UTC - smoke tests
workflow_dispatch:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
# ── Fast gate: typecheck + build ──
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Type check
run: npx tsc --noEmit
- name: Build
run: npm run build
# Guard: committed cli-manifest.json must match the one build regenerates.
# Prevents silent drift where unrelated adapter entries vanish or change
# across PRs (agent hits unexpected manifest diff → surgical-merge churn).
- name: Check cli-manifest.json is up-to-date
if: runner.os == 'Linux'
shell: bash
run: |
if ! git diff --exit-code -- cli-manifest.json; then
echo "::error::cli-manifest.json is out of sync with the source. Run 'npm run build' and commit the result."
exit 1
fi
# Guard: adapter rows must not silently emit keys omitted from `columns`.
# Existing findings are tracked in scripts/silent-column-drop-baseline.json;
# this gate rejects newly introduced drops while allowing incremental cleanup.
- name: Check silent column drops
if: runner.os == 'Linux'
run: npm run check:silent-column-drop
# Guard: adapters should fail with typed errors instead of silently
# returning empty arrays, clamping user input, or inventing sentinel data.
# Existing findings are tracked in scripts/typed-error-lint-baseline.json.
- name: Check typed-error lint baseline
if: runner.os == 'Linux'
run: npm run check:typed-error-lint
# ── Unit tests (vitest shard) ──
# PR: ubuntu + Node 22 only (fast feedback, 2 jobs).
# Push to main/dev: full matrix for cross-platform/cross-version coverage (12 jobs).
unit-test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && fromJSON('["ubuntu-latest","macos-latest","windows-latest"]') || fromJSON('["ubuntu-latest"]') }}
node-version: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && fromJSON('["22"]') || fromJSON('["22"]') }}
shard: [1, 2]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests (Node ${{ matrix.node-version }}, shard ${{ matrix.shard }}/2)
run: npx vitest run --project unit --project extension --reporter=verbose --shard=${{ matrix.shard }}/2
# ── Bun compatibility check ──
bun-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests under Bun
run: bun vitest run --project unit --reporter=verbose
# Adapter tests are pure unit tests - OS doesn't affect results. Gated off
# `pull_request` to keep PR CI under ~2 minutes; adapter authors run focused
# tests locally before pushing, and `push` to main / nightly cron / manual
# dispatch still guard the merged state.
adapter-test:
if: github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run focused adapter tests
run: npm run test:adapter -- --reporter=verbose
# ── Smoke tests (scheduled / manual only) ──
smoke-test:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
needs: build
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# NOTE: Windows excluded - browser-actions/setup-chrome hangs during
# Chrome MSI installation on Windows runners (known issue).
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Setup Chrome
uses: ./.github/actions/setup-chrome
id: setup-chrome
- name: Build
run: npm run build
- name: Run smoke tests (Linux, via xvfb)
if: runner.os == 'Linux'
run: |
xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24" \
npx vitest run tests/smoke/ --reporter=verbose
- name: Run smoke tests (macOS / Windows)
if: runner.os != 'Linux'
run: npx vitest run tests/smoke/ --reporter=verbose
timeout-minutes: 15
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: CI on: push: branches: [main, dev] pull_request: branches: [main, dev] schedule: - cron: '0 8 * * 1' # Weekly Monday 08:00 UTC - smoke tests workflow_dispatch: concurrency: group: ci-${{ github.ref }} cancel-in-progress: true jobs: # ── Fast gate: typecheck + build ── build: timeout-minutes: 30 runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' - name: Install dependencies run: npm ci - name: Type check run: npx tsc --noEmit - name: Build run: npm run build # Guard: committed cli-manifest.json must match the one build regenerates. # Prevents silent drift where unrelated adapter entries vanish or change # across PRs (agent hits unexpected manifest diff → surgical-merge churn). - name: Check cli-manifest.json is up-to-date if: runner.os == 'Linux' shell: bash run: | if ! git diff --exit-code -- cli-manifest.json; then echo "::error::cli-manifest.json is out of sync with the source. Run 'npm run build' and commit the result." exit 1 fi # Guard: adapter rows must not silently emit keys omitted from `columns`. # Existing findings are tracked in scripts/silent-column-drop-baseline.json; # this gate rejects newly introduced drops while allowing incremental cleanup. - name: Check silent column drops if: runner.os == 'Linux' run: npm run check:silent-column-drop # Guard: adapters should fail with typed errors instead of silently # returning empty arrays, clamping user input, or inventing sentinel data. # Existing findings are tracked in scripts/typed-error-lint-baseline.json. - name: Check typed-error lint baseline if: runner.os == 'Linux' run: npm run check:typed-error-lint # ── Unit tests (vitest shard) ── # PR: ubuntu + Node 22 only (fast feedback, 2 jobs). # Push to main/dev: full matrix for cross-platform/cross-version coverage (12 jobs). unit-test: timeout-minutes: 30 runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && fromJSON('["ubuntu-latest","macos-latest","windows-latest"]') || fromJSON('["ubuntu-latest"]') }} node-version: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && fromJSON('["22"]') || fromJSON('["22"]') }} shard: [1, 2] steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run unit tests (Node ${{ matrix.node-version }}, shard ${{ matrix.shard }}/2) run: npx vitest run --project unit --project extension --reporter=verbose --shard=${{ matrix.shard }}/2 # ── Bun compatibility check ── bun-test: timeout-minutes: 30 runs-on: latchkey-small steps: - uses: actions/checkout@v6 - uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.5 - uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' - name: Install dependencies run: npm ci - name: Run unit tests under Bun run: bun vitest run --project unit --reporter=verbose # Adapter tests are pure unit tests - OS doesn't affect results. Gated off # `pull_request` to keep PR CI under ~2 minutes; adapter authors run focused # tests locally before pushing, and `push` to main / nightly cron / manual # dispatch still guard the merged state. adapter-test: timeout-minutes: 30 if: github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' runs-on: latchkey-small needs: build steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' - name: Install dependencies run: npm ci - name: Run focused adapter tests run: npm run test:adapter -- --reporter=verbose # ── Smoke tests (scheduled / manual only) ── smoke-test: if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' needs: build runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: # NOTE: Windows excluded - browser-actions/setup-chrome hangs during # Chrome MSI installation on Windows runners (known issue). os: [ubuntu-latest, macos-latest] steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' - name: Install dependencies run: npm ci - name: Setup Chrome uses: ./.github/actions/setup-chrome id: setup-chrome - name: Build run: npm run build - name: Run smoke tests (Linux, via xvfb) if: runner.os == 'Linux' run: | xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24" \ npx vitest run tests/smoke/ --reporter=verbose - name: Run smoke tests (macOS / Windows) if: runner.os != 'Linux' run: npx vitest run tests/smoke/ --reporter=verbose timeout-minutes: 15
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
1 third-party action is referenced by a movable tag. Pin it to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
This workflow runs 5 jobs (9 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.