Follow-up to the CI-topology change that removed the main push trigger and made the release reuse its PR's full-CI gate. That change eliminated whole runs; these three make each remaining run cheaper. They are independent — ship them separately.
1. Stop doing full-history checkouts (biggest win, hits every PR)
ci.yml does fetch-depth: 0 in four jobs (impact, server, client, windows-server) on a ~13k-commit / ~100 MiB repo. Nothing needs full history:
scripts/ci-test-plan.js needs only git diff <base>...<head> and git ls-files.
scripts/run-ci-tests.js needs vitest --changed <baseSha>, i.e. the base tree, not its ancestry.
- On full runs (nightly, dispatch, release fallback, and the
main → release PR) CI_FORCE_FULL skips the diff entirely, so impact needs no history at all and the other three touch git not at all.
Cheapest version is one line per job:
fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }}
The fuller version is depth-1 everywhere plus a targeted git fetch --depth=1 origin $BASE_SHA where the base is actually needed.
Estimated: ~10–15s per job, ~40–60 billed runner-seconds per run — and because impact gates every other job, ~10–15s off the wall-clock critical path of every PR.
Verify: a scoped PR plan and a full plan both still select the same test files as they do today.
2. Cache server/node_modules instead of reinstalling it three times
npm ci --prefix server + node scripts/trusted-rebuilds.js server run in server, database, and windows-server. setup-node's npm cache only preserves ~/.npm (the tarball cache) — npm ci still wipes and repopulates node_modules, and the rebuild recompiles node-pty and re-runs onnxruntime-node's postinstall, which downloads platform binaries, on each of the three.
Cache server/node_modules keyed on hashFiles('server/package-lock.json'), folding the native rebuild into the cache-miss path.
Estimated: ~60–90s on each of the two redundant jobs, ~2–3 billed minutes per full run. Windows is the biggest single winner at 2× billing.
Careful: server/.npmrc pins ignore-scripts=true, so a restored cache must still be a rebuilt one — cache after the rebuild, not before, or the native addons are unusable.
3. Retire the lint and test (24.x) legacy check names
The lint job burns a whole runner to echo — it exists only to mirror the client job's lint result under a historical required-check name, and server carries name: test (24.x) for the same reason. GitHub bills a 1-minute minimum per job plus provisioning.
Neither main nor release currently has branch protection configured at all, so nothing is actually requiring these contexts today. Confirm that, then drop the lint job and rename server to something honest. CI Gate (and now Full CI Gate) are the checks that matter.
Estimated: ~1 billed minute per run.
Careful: if branch protection is ever added, add it requiring CI Gate, not the legacy names.
Context
Numbers measured 2026-08-21 against a full run (~10 min wall, ~38 billed runner-minutes: server ~7min, client+build ~7min, Windows ~10min at 2×). Baseline for comparison lives in docs/GITHUB_ACTIONS.md under "Vitest runner tuning".
Follow-up to the CI-topology change that removed the
mainpush trigger and made the release reuse its PR's full-CI gate. That change eliminated whole runs; these three make each remaining run cheaper. They are independent — ship them separately.1. Stop doing full-history checkouts (biggest win, hits every PR)
ci.ymldoesfetch-depth: 0in four jobs (impact,server,client,windows-server) on a ~13k-commit / ~100 MiB repo. Nothing needs full history:scripts/ci-test-plan.jsneeds onlygit diff <base>...<head>andgit ls-files.scripts/run-ci-tests.jsneeds vitest--changed <baseSha>, i.e. the base tree, not its ancestry.main→releasePR)CI_FORCE_FULLskips the diff entirely, soimpactneeds no history at all and the other three touch git not at all.Cheapest version is one line per job:
The fuller version is depth-1 everywhere plus a targeted
git fetch --depth=1 origin $BASE_SHAwhere the base is actually needed.Estimated: ~10–15s per job, ~40–60 billed runner-seconds per run — and because
impactgates every other job, ~10–15s off the wall-clock critical path of every PR.Verify: a scoped PR plan and a full plan both still select the same test files as they do today.
2. Cache
server/node_modulesinstead of reinstalling it three timesnpm ci --prefix server+node scripts/trusted-rebuilds.js serverrun inserver,database, andwindows-server.setup-node's npm cache only preserves~/.npm(the tarball cache) —npm cistill wipes and repopulatesnode_modules, and the rebuild recompiles node-pty and re-runs onnxruntime-node's postinstall, which downloads platform binaries, on each of the three.Cache
server/node_moduleskeyed onhashFiles('server/package-lock.json'), folding the native rebuild into the cache-miss path.Estimated: ~60–90s on each of the two redundant jobs, ~2–3 billed minutes per full run. Windows is the biggest single winner at 2× billing.
Careful:
server/.npmrcpinsignore-scripts=true, so a restored cache must still be a rebuilt one — cache after the rebuild, not before, or the native addons are unusable.3. Retire the
lintandtest (24.x)legacy check namesThe
lintjob burns a whole runner toecho— it exists only to mirror the client job's lint result under a historical required-check name, andservercarriesname: test (24.x)for the same reason. GitHub bills a 1-minute minimum per job plus provisioning.Neither
mainnorreleasecurrently has branch protection configured at all, so nothing is actually requiring these contexts today. Confirm that, then drop thelintjob and renameserverto something honest.CI Gate(and nowFull CI Gate) are the checks that matter.Estimated: ~1 billed minute per run.
Careful: if branch protection is ever added, add it requiring
CI Gate, not the legacy names.Context
Numbers measured 2026-08-21 against a full run (~10 min wall, ~38 billed runner-minutes: server ~7min, client+build ~7min, Windows ~10min at 2×). Baseline for comparison lives in
docs/GITHUB_ACTIONS.mdunder "Vitest runner tuning".