- GitHub Actions best practices
- Cut repeated setup and teardown from your test suite
Cut repeated setup and teardown from your test suite
Do expensive fixture work once per suite or per schema, and reset only the state a test reads, so each test spends its time on what it verifies.
How StarSling worksAI agents open the PR
StarSling agents inspect your workflow, apply this optimization, and open a reviewable PR automatically.
Set up shared resources once per suite or schema, reset test data in process, and keep cleanup lists limited to pending work. Use fast test-only defaults for intentionally expensive production operations, and keep real-path tests for the behavior each optimization touches.123456
- name: Profile CPU-heavy setup in Vitest
run: >-
pnpm exec vitest run --no-file-parallelism
--execArgv=--cpu-prof
--execArgv=--cpu-prof-dir=test-runner-profile
- name: Save CPU profiles
uses: actions/upload-artifact@v7
with:
name: test-runner-profile
path: test-runner-profile/*.cpuprofileRepeated schema resets, package resolution, cleanup and hashing add up across every test group.
- name: Run tests
run: pnpm test:run
# Changing fixture code without timing the full suite hides the effect.Shipped by StarSling
Four merged Better Auth changes remove repeated work inside the test suite. Each card names its measured scope: test execution step, Vitest duration, or test job duration.
- Merged 2026-10-19
Public GitHub PR
1.09× faster prisma adapter tests
Test execution step: 301.9 s → 277.1 s across 10 runs per version (20 jobs), same 1,328 tests.
Reuse the schema and reset test data
A schema-keyed reset clears data through the existing Prisma client.
View PR #9882 in better-auth/better-authCustomer case study
How Better Auth got 2× faster E2E tests and cut 20,000 CI minutes a month with StarSling
Per-job E2E from 2m 22s down to 1m 04s. Agent-native CI for the auth library every team's PR queue gates on.
Read the Better Auth customer story - Merged 2026-10-19
Public GitHub PR
1.16× faster drizzle adapter tests
Test execution step: 220.0 s → 190.1 s across 50 runs per version (100 jobs).
Resolve drizzle-kit once
The suite starts the resolved CLI with the Node executable.
View PR #10850 in better-auth/better-auth - Merged 2026-09-03
Public GitHub PR
Up to 5.4× faster
Mongo adapter Vitest duration: 24.3s → 4.5s in the PR’s benchmark, with 50 runs per arm.
Keep only pending cleanup rows
Cleanup retains outstanding work and explicit retries.
View PR #10762 in better-auth/better-auth - Merged 2026-08-20
Public GitHub PR
≈18% faster test job
Mean test job on Node 22: 359s → 296s, and on Node 24: 342s → 280s, across 50 runs per arm (200 jobs total).
Use a fast test-only password hasher
The shared fixture uses a fast default and preserves caller overrides.
View PR #10879 in better-auth/better-auth
Better Auth #9882 records the last schema pushed per database dialect. When it matches, the existing Prisma client clears table data in process: PostgreSQL uses TRUNCATE ... RESTART IDENTITY CASCADE, and MySQL uses DELETE in one interactive transaction so session settings stay on the same connection. A changed schema or an unsuccessful reset takes the full prisma db push --force-reset path. SQLite keeps its original push path. Preserve test isolation, and keep coverage of the real schema-push path.
Fetch OPT85 "Tests Run in Many Processes Instead of One" from https://raw.githubusercontent.com/starslingdev/skills/main/skills/ci-speedup/references/optimization-patterns.md and check this repo against it yourself. Find the slowest test job gating the merge and read its step timings over the last 20 runs of its workflow with the GitHub CLI (gh run list, then gh run view --json jobs for each run). It applies when that suite's time goes to starting processes: tests that launch a subprocess, interpreter or CLI for each case, or a Rust crate with many integration-test binaries under tests/, each linked and started on its own. Measure it before changing anything: time one case through a subprocess against the same check called in-process, or count the test binaries and time the suite. If it applies, follow the entry's recipe: call the code in-process wherever the test does not exist to verify process behavior, or merge the integration tests into a single test binary, keeping every test case and assertion. Keep at least one test that runs the real process end to end. Report the suite's runtime before and after on the same machine. If nothing qualifies, tell me in one line why. Show me the diff and open a PR rather than applying it blindly.Better Auth #10850 resolves the drizzle-kit CLI once and starts it with process.execPath. The adapter suite still runs all 407 schema pushes, with the same command options and database effects. Each launch becomes cheaper because it starts the resolved executable directly. Measure package resolution separately from the schema work to choose this fix.
Better Auth #10762 makes cleanup forget processed rows. Previously, each teardown revisited the rows created by earlier tests; 101,366 of 103,554 deletes targeted rows already gone. Retain rows whose cleanup throws or explicitly requests a retry, following the helper’s result contract. Check successive cleanup passes as well as the final database state.
Better Auth #10879 gives the shared fixture a fast salted SHA-256 hash and verify pair. Caller-supplied implementations and normalization stay intact, and production keeps its existing hasher. Keep the production hasher explicitly enabled in tests that verify stored-hash formats, upgrades, compatibility or security behavior. Use the faster default for tests whose subject is the surrounding application behavior.
A small setup cost becomes a large part of runtime when hundreds of test groups repeat it. Profile the suite before adding runners: reducing repeated work helps every run, including local development. Use the slow-test diagnosis to locate the cost, and see the Better Auth case study for the wider CI work.
Use it when
Use this approach when profiling shows shared fixtures, schema resets, CLI startup or teardown consuming a meaningful share of test time. Start with the slowest test job gating the merge.
Be careful when
Keep per-test setup when a test verifies initialization, migration, process behavior or isolation itself. Keep production cryptography in security and compatibility tests, and use separate database state for concurrent workers.
Hand this prompt to your coding agent (Claude Code, Cursor, and the like) to audit and fix this practice in your own repo.
Diagnose why the test job in this repo's GitHub Actions workflow runs much slower than the same suite does locally. Do not change anything yet. First, open the workflow YAML under .github/workflows/ and identify the test job: check whether it uses a strategy.matrix with a --shard flag (Playwright, vitest), and whether it's the single longest job in the workflow's run history. Second, check whether the test job's install step has a matching caching action (actions/cache, or setup-node/setup-python/setup-go with cache: true) for its package manager. Third, profile shared fixture setup and search test helpers for production-strength password hashing or other deliberately expensive cryptography; if it dominates, propose a fast test-only default while preserving normalization and explicit overrides, and never change the production hasher. Keep the production implementation explicitly enabled in tests that verify stored-hash formats, upgrades, compatibility, or security behavior. Fourth, inspect teardown bookkeeping for cleanup passes that revisit successfully deleted IDs; propose removing IDs only after confirmed successful cleanup and retaining every unsuccessful or deferred cleanup according to the helper's result contract. Better Auth #10762 retains thrown failures and explicit `retry` results. Fifth, if this is a monorepo (turbo.json or nx.json present), check whether the test command actually scopes to the diff against the merge base rather than running everything. Sixth, grep the test source (packages/, e2e/, tests/) for fixed-duration waits like waitForTimeout, cy.wait(1000+), or raw sleep() calls, and estimate their total contribution to run time. Report back which causes you found evidence for, ranked by likely impact, before proposing any fix.Prefer to check by hand?
For the Vitest example, download the CPU profiles and open them in Chrome DevTools or VS Code to locate fixture and hook CPU work such as hashing. Add wall-clock timers around database calls and subprocess launches to measure their elapsed time. Keep profiling and concurrency settings identical in before-and-after runs.
Record setup, test-body and teardown timings before editing the shared helper. Count subprocess launches and cleanup operations across successive test groups.
Run both versions on the same runner type with the same test command, inputs and concurrency. Repeat the measurements and compare the test execution step.
Confirm the same test files, cases and assertions run before and after. Check that schema changes, concurrent workers, cleanup retries and caller overrides preserve their behavior.
Keep end-to-end coverage of the real CLI and production hasher where those behaviors are under test.
Go further
One fix, all of them, or forever.
You have the prompt for this one practice. Here is how much further you can take it, each step doing more for you than the last.
Fix this one thing
Copy the prompt above
Hand OPT85 to your coding agent and fix it in your repo today.
OPT85 covers the process-startup work in the schema-reset example: Better Auth #9882 replaces repeated CLI launches with an in-process reset.
Fix everything, once
Install the ci-speedup skill
One prompt audits your whole repo against all 91 ci-speedup patterns (this one plus 90 more) and hands your agent every fix at once. Open source, MIT, runs locally.
Keep it fixed, forever
Install the StarSling GitHub App
Connect GitHub and the fixes stay applied as your CI evolves, with agents that keep inspecting your workflows and opening optimization PRs you review.
More best practices for GitHub Actions
Where to go next in the CI best-practices catalog.
- Parallelization
Shard tests across parallel jobs in GitHub Actions
Speed up GitHub Actions tests by splitting one long test job into parallel shards with a matrix, so the suite finishes in a fraction of the wall-clock time.
- Runner & Queue
Replace fixed CI sleeps with bounded readiness polling
Poll the exact condition a test needs, stop as soon as it is true, and cap the wait with a diagnostic failure so fast runs do not pay a worst-case sleep and slow runs do not become flaky.
- Runner & Queue
Wait for container healthchecks instead of sleeping
Give every service container a healthcheck that proves it can answer a real request, then start the stack with
docker compose up -d --waitso the job blocks exactly until each service is ready. - Caching & Setup
GitHub Actions cache: dependencies, keys, and cache hits
The GitHub Actions cache reuses package-manager downloads between workflow runs.
More ways to improve GitHub Actions
If you do not know why a run is slow yet, start with the diagnostic guide. Let an agent find which of these applies: /ci-speedup. If your workflows are already optimized but still slow, see our guide to fast GitHub Actions and GitHub Actions runner alternatives. Building containers in CI? The Docker workflow guide covers layer caching end to end. Want to see how your configuration measures up before changing anything? CI Score grades workflow config against a pass/fail rubric of these practices. It is not a speed measurement. To find exploitable workflow paths before attackers do, ci-secure checks the ten critical GitHub Actions attack vectors and lets you choose which fixes to apply.
- GitHub Actions too slow
- Fast GitHub Actions
- GitHub Actions alternatives
- GitHub Actions pricing
- Self-hosted GitHub Actions runners
- GitHub Actions CI/CD pipeline
- Levels of CI: every way to make GitHub Actions cheaper and faster
- Docker CI on GitHub Actions
- Install the free /ci-score skill to improve your GitHub Actions setup
- Install the /ci-secure skill to close critical attack vectors in GitHub Actions
- Browse GitHub Actions agent skills
FAQ
How do I find repeated test setup?
Profile shared setup and teardown hooks in the slowest suite. Count schema pushes, CLI launches, cleanup queries and password hashes, then compare their total time with test-body execution.
Can suite-level setup preserve test isolation?
Yes, when each case starts with the state it expects. Share stable schema and process initialization, reset mutable data between cases, and isolate concurrent workers. Exercise schema changes and reset recovery in dedicated tests.
Should I shard the suite first?
Measure the bottleneck first. Removing repeated fixture work reduces total execution; sharding distributes the remaining work. Apply both when each has a measurable benefit.
Sources
1Vitest test performance profiling (opens in new tab)
2Prisma db push (opens in new tab)
3Better Auth #9882 (opens in new tab)
4Better Auth #10850 (opens in new tab)
Last updated 2026-10-27