CI and the checks you run at home
What each Markless CI job runs, how pnpm ci:local runs the same commands on your machine, and the rules that keep CI honest.
Last page, you ran pnpm ci:local --fast before you pushed. So what does that command run?
scripts/ci/local.mjs reads .github/workflows/ci.yml each time it runs. It runs each job’s own run: steps, so your machine and CI run the same commands.
Pick a mode and see which jobs run.
Which CI jobs run on my machine?
Pick --fast, --full or CI only. The jobs that run light up.
$ pnpm ci:local --fast
fast
agent-filesrunsruler apply, then fail on drifttypecheckrunspnpm typecheck, vp checkunitrunsvp test --project node
full
browserdoes not runvp test --project browser, uicompletion-matrixdoes not runtest:completion-matrixboxes-bundlerdoes not runtest:boxes in packages/bundlerboxes-routerdoes not runtest:boxes in packages/routerboxes-music-playerdoes not runtest:analyzer, test:boxesboxes-music-player-ssrdoes not runtest:analyzer, test:boxesreceiptsdoes not runreceipts:generate, receipts:checkpackage-manager-matrixdoes not runworkspace-matrix.test.ts
CI only
benchmarkdoes not runJS Framework Benchmark against a baselinebenchmark-guarddoes not runcompare with the baseline
ci:local never runs them. test is the gate on GitHub.- noteThis is the default. Plain pnpm ci:local runs the same jobs.
- noteEach job runs its own run: steps from ci.yml.
From .github/workflows/ci.yml and the mode table in scripts/ci/local.mjs, as pnpm ci:local --list prints them. Commands are shortened.
Notice that --full adds eight jobs to the three fast ones. Two benchmark jobs never run on your machine.
How it sorts the steps
- check: a
run:step that tests something. It runs. - setup:
pnpm install,corepack enableand the Playwright installs. They run only with--install. - skip:
uses:actions, lane cache markers, and steps that read GitHub context. These are CI plumbing.
The script owns one small table that puts each job in a mode. A new job with check steps but no entry makes the script exit with an error that names the job.
The jobs
| Job | Main command | Mode |
|---|---|---|
agent-files |
pnpm dlx @intellectronica/ruler apply, then fails on drift |
fast |
typecheck |
node scripts/ci/check-workflow.mjs .github/workflows/ci.yml, pnpm typecheck, pnpm exec vp check --no-fmt |
fast |
unit |
pnpm exec vp test --project node |
fast |
browser |
pnpm exec vp test --project browser, pnpm exec vp test --project ui |
full |
completion-matrix |
pnpm --dir packages/typescript-plugin test:completion-matrix |
full |
boxes-bundler |
pnpm --dir packages/bundler test:boxes |
full |
boxes-router |
pnpm --dir packages/router test:boxes |
full |
boxes-music-player |
test:analyzer and test:boxes in demos/music-player |
full |
boxes-music-player-ssr |
test:analyzer and test:boxes in demos/music-player-ssr |
full |
receipts |
pnpm receipts:generate, pnpm receipts:check |
full |
package-manager-matrix |
pnpm exec vitest run packages/cli/test/workspace-matrix.test.ts --project=node |
full |
benchmark, benchmark-guard |
JS Framework Benchmark runs against a baseline, then a compare | CI only |
lanes, prepare-playwright, save-lane-markers, test and changes route work and have no checks. test is the gate. It fails unless every test lane passed or hit its cache.
Run it at home
pnpm ci:local --list # every job and step, and its mode
pnpm ci:local --fast # agent-files, typecheck, unit (the default)
pnpm ci:local --full # every job this machine can run
pnpm ci:local --job browser --job boxes-router # only these jobs
pnpm ci:local --fast --clean # in a throwaway worktree of HEAD
pnpm ci:local --dry-run # print the commands, run nothing
--clean uses the committed tree, so an untracked file cannot make a check pass. --linux runs each job in a Linux container. It needs docker, and without it the script exits with code 2.
Two more workflows
screen-reader.ymlruns for changes underpackages/headless/. It has avirtuallane, annvdalane on Windows and avoiceoverlane on macOS.release.ymlruns only by hand. It has adry-runmode and apublishmode, and it never bumps versions.
Never weaken a check
When a check fails, sort the failure first: environment drift, your regression, an older failure, or a flaky test. Then fix the cause.
- Do not skip, delete or loosen a test to make it pass.
- Do not add retries to Vitest, Playwright or the workflow.
- Raise a timeout only with a measured reason.
- Change a perf guard anchor only with
pnpm perf:guard:accept <guard> "<subject>" --reason "<one line>". The command fails without--reason.
A flaky test fails and passes on the same code. Fix its cause, such as a fixed port or a fixed poll window. If you cannot fix it now, docs/ci-process.md describes a quarantine with an owner and an expiry date.
Next: Where do the rules behind these checks live? Specs and rules →
