CI / CD
GitHub Actions workflows live in .github/workflows/. This document explains what each one does, when it runs, and what to do when it’s red.
Workflows
| File | Trigger | Purpose | Required to merge? |
|---|---|---|---|
ci-smoke-tests.yml | Pull request (paths: apps/**, libs/**, lockfiles, nx.json, Makefile, .env.test.example, workflows) | DB migration + API unit/integration/e2e + Web unit/integration | Yes |
cd-full-test-gate.yml | Push to main, manual dispatch | Everything in smoke + Playwright web E2E | Yes (blocks deploy) |
deploy-pr-preview.yml | Pull request | Builds a preview environment for review | No, but useful |
publish-shared.yml | When libs/shared/** changes | Publishes the shared lib (if applicable) | No |
smegrep.yml | Manual | Adhoc grep-based checks | No |
Smoke workflow (PRs)
ci-smoke-tests.yml — runs on every PR that touches code/lockfiles/Makefile/workflows. Goes:
- Stand up Postgres (pgvector/pg16) on port 55432 and Redis on 6379.
- Set test env (
TEST_AUTH_BYPASS=true,TEST_HOOKS_ENABLED=true,CRONS_ENABLED=true, Clerk test creds from secrets). - Migrate the DB.
- Run API: unit → integration → e2e.
- Run Web: unit → integration.
- Cancel duplicates of the same ref via
concurrency.cancel-in-progress.
Total budget: 45 minutes (timeout-minutes).
When it’s red
- API tests: rerun locally with the same env (
make test-unit-api,make test-integration-api,make test-e2e-api). Most flakes are timing-related — see testing/strategy.md. - Web tests:
make test-unit-web,make test-integration-web. - Migration step: the journal-monotonicity bug (migrations.md) sometimes appears as “migration applied successfully” but a follow-up assertion fails because the schema didn’t change. Check
libs/db/drizzle/meta/_journal.jsonfor any out-of-orderwhenvalues. - Auth bypass surprises:
TEST_AUTH_BYPASS=trueletsx-test-user-idheader replace Clerk. If a test fails because the user isn’t found, the seed step probably didn’t run — check thetestApi.seedX()calls in the spec.
Full test gate (main / pre-deploy)
cd-full-test-gate.yml — runs on push to main and manual dispatch. Includes Playwright web E2E (which the smoke workflow skips).
Total budget: 75 minutes.
When it’s red
Same diagnostic steps as smoke, plus Playwright artifacts: traces, screenshots, video are uploaded as workflow artifacts. Download from the GitHub Actions UI. See testing/driver-pattern.md for the e2e architecture.
A red full-gate blocks deploy. Don’t bypass it — investigate first.
PR preview
deploy-pr-preview.yml — builds a preview env for visual review. Not enforced, but useful for stakeholder sign-off before merge.
Required GitHub secrets
For both ci-smoke-tests.yml and cd-full-test-gate.yml:
CI_TEST_DATABASE_URL— points at a self-hosted CI Postgres if you don’t use the workflow’s bundledservices.postgres. Currently the workflows boot their own Postgres so this is unused unless you point them elsewhere.CLERK_SECRET_KEY,CLERK_PUBLISHABLE_KEY— a Clerk test instance dedicated to CI.
For the full gate only:
E2E_CLERK_USER_EMAIL,E2E_CLERK_USER_PASSWORD,E2E_CLERK_USER_ID— credentials for the E2E sign-in flow.
Add new secrets via Repo Settings → Secrets and Variables → Actions.
Local “act-like-CI”
To reproduce the smoke env locally:
make test-db-up
make test-db-migrate
make test-local-smoke # unit + integration for API & web (no Playwright)
make test-local-all # the above + Playwright web e2eAdding a workflow
Pre-flight:
- Use
concurrency.cancel-in-progress: truefor everything that doesn’t deploy. - Set
timeout-minutesso a hung step doesn’t burn the runner quota. - Pin actions to a major version, not
@main. - If the workflow needs secrets, document them in this file.