CI
The GitHub Actions workflow that runs the checks, the database tests, both builds and every page of the app in Chrome on every pull request, with no secrets and no cost.
What
.github/workflows/ci.yml runs one job on ubuntu-latest for every pull request to main and
every push to main:
| Step | Command | About |
|---|---|---|
| Database | docker compose up -d db | Postgres 18 + pgvector, started first so it downloads in the background |
| Install | bun install --frozen-lockfile | Bun 1.4.4, as on a laptop; also runs Panda's codegen |
| Checks | bun run check | Types, lint, format, 556 tests |
| Database tests | bun run check:db | 443 tests against the compose database |
| Web build | bun run build | next build for apps/web |
| Demo runs | bun run db:seed-demo | 48 made-up runs in a database of their own, vektos_demo |
| App pages | next start, then bun run check:app | Every page in Chrome at 1280 and 390 px, light and dark: a heading, no overflow, axe |
| Docs build | bun run docs:build | next build for the docs site |
A run takes about three minutes. A new push to a pull request cancels the run of the previous one.
Why
- The checks of a block ran on one Mac. A file that only exists there (a generated type, a
local
.env) would pass every time, and nobody would know until a fresh clone. bun run checkdoes not build the app. A design-system module that ran client code when it loaded broke every page and passed every check (anecdote 15).next buildfails on it.- The database tests need a database. They are a separate command so a laptop without Docker
can still run
check; CI has Docker, so it runs both. - A page can build and still break.
check:appopens every page of the built app, a run's page and a model's page included, and fails on a missing heading, a page wider than a phone or an axe violation. It needs runs to show:db:seed-demowrites two fake models (demo/alpha,demo/beta) at two contexts on three real tasks, under two grader versions, so even/regressionshas a comparison, and oneworse, to draw. The numbers are fixed inpackages/bench/src/demo.ts; no model is called. The auth secret in the workflow is a placeholder: nobody signs in. - No secrets, so no cost. The workflow has no AI Gateway key: nothing in it can call a model, create embeddings or open the chat. Benchmark runs stay a decision made by a person. GitHub's free plan includes 2,000 Linux minutes a month for a private repository and has a $0 spending limit by default; a public repository does not count minutes at all.
Try it
Open a pull request: the run appears under the PR's checks. From a terminal:
gh pr checks --watch # wait for every check the PR lists now
gh pr checks | awk -F'\t' '$1=="check"{print $2}' # CI's own verdict: it must print "pass"
gh run list --limit 5 # the latest runs
gh run view <id> --log-failed # only the failing step's outputSince the deploy a pull request lists five checks: CI's job, named check, and four from Vercel
(a preview of each project, and its comments). Only check says the code is right. Vercel's are
green in about a minute, and --watch waits for the checks that exist: while a CI run is queued
and has no job yet, it is not in the list, and the command ends with four passes and exit 0
(anecdote 29). A run that stays queued:
gh run list shows it; closing and reopening the pull request starts another.
To reproduce a run on a laptop, from a fresh clone:
git clone <repo> /tmp/vektos && cd /tmp/vektos
bun install --frozen-lockfile
bun run check && bun run db:up && bun run check:db && bun run build && bun run docs:buildThe app's pages on demo runs, on a laptop, without touching the real database:
docker exec vektos-db-1 psql -U vektos -d vektos -c "create database vektos_demo"
export DATABASE_URL=postgres://vektos:vektos@127.0.0.1:5433/vektos_demo
bun run db:migrate && bun run db:seed-demo # "16 demo batches and 48 runs written."
bun run build && BETTER_AUTH_SECRET=local-placeholder-0123456789abcdef bun run --cwd apps/web start -- --port 3100
VEKTOS_APP_URL=http://localhost:3100 bun run check:app # 4 passExtend it
- The page check of the benchmark (
bun run check:page) needs Chrome too, which the runner already has (check:appuses it); it also needs the design system's Storybook build and a scratch app, about a minute more. - A new page goes in
PAGESinapps/web/src/app.browser.test.ts. A page that needs an id is found from a link, as the run and model pages are. - More demo data (a new metric, a run with screenshots) goes in
demo.ts; its test indemo.test.tspins what the pages can count on. - Another check is one more step. Keep the expensive ones last, so a cheap failure stops the run early.
- Never add the gateway key to run a benchmark in CI. A scheduled benchmark is a decision about money, and belongs to a separate, manually started workflow with its own budget cap.
Known gaps
- GitHub does not block a red merge. Required status checks need a paid plan for a private
repository. The rule is in
AGENTS.md: merge only when the run is green. Once the repository is public, a ruleset can require thecheckjob. check:page(Chrome, 229 tests) runs only on a laptop.- The demo runs have no screenshots, steps or chat history, so the parts of the pages that draw them are checked empty.
ubuntu-latestmoves to new Ubuntu releases on GitHub's schedule; pinubuntu-24.04if a move breaks something.- CI does not ship, and does not stop a deploy. Vercel builds every push on its own, a red
mainincluded (guide 22).
Payload check
What the runner really sends to a model through the AI SDK, compared with the same loop written with the official SDKs. Offline, no key, no cost.
Generative UI
A catalog of the governed design system that a model composes a view from, and the renderer that checks every part and says what it left out.