Contribution Workflow
Git hooks, CI pipeline, commit conventions, and pull request process.
This page covers the mechanics of contributing to Toast — from making your first commit to getting a PR merged.
The Golden Rule
Run pnpm check before pushing. It auto-fixes lint and formatting issues, then runs typecheck, tests, and build. If it passes, your code is ready.
pnpm check # Auto-fix, then verify everything
pnpm check:strict # Verify only, no auto-fix (what CI runs)Git Hooks
Toast uses Husky to run automated checks at key git lifecycle points. Never bypass hooks with --no-verify — fix the underlying issue instead.
commit-msg
Validates your commit message format using commitlint:
pnpm commitlint --edit "$1"Rejects commits that don't follow the conventional commit format (see Commit Format below).
pre-commit
Runs lint-staged to auto-fix staged files:
- TypeScript/JavaScript — Biome lint fix + Prettier format
- JSON/CSS/YAML/Markdown — Prettier format
- SQL migrations — Syncs
docs/patterns/database.mdmigration log
Changes made by lint-staged are automatically re-staged, so your commit includes the fixes.
pre-push
The final quality gate before code leaves your machine:
pnpm check:strictThis runs lint, format check, typecheck, tests with coverage enforcement, and build — all in check-only mode (no auto-fix). If the pre-commit hook did its job, this should pass cleanly.
Commit Format
Toast enforces Conventional Commits:
<type>(optional-scope): short summaryAllowed Types
| Type | Use When |
|---|---|
feat | Adding a new feature |
fix | Fixing a bug |
refactor | Restructuring code without changing behavior |
test | Adding or updating tests |
docs | Documentation changes |
chore | Maintenance tasks (dependencies, configs) |
build | Build system or external dependency changes |
ci | CI/CD pipeline changes |
perf | Performance improvements |
style | Code style changes (formatting, semicolons) |
revert | Reverting a previous commit |
Examples
feat(api): add content filter by author
fix(admin): handle empty upload response
docs: update Railway deployment troubleshooting
refactor(db): extract shared query helpers
test(api): add coverage for audit log paginationCommit Body
The body should explain why the change was made. The diff shows what changed; the message explains why it matters:
fix(api): return 404 for unpublished content in public API
The public content endpoint was returning draft posts to unauthenticated
users. This scopes the query to published status only, matching the
behavior described in the Content API docs.
Closes #456CI Pipeline
Every push and PR triggers the CI workflow (.github/workflows/ci.yml) with seven jobs:
Job Dependency Graph
pr-title ─────────────┐
commit-messages ───────┤
quality ───────────────┼─→ ci-gate ─→ docker-build
coverage ──────────────┤
integration-tests ─────┘Job Details
| Job | What It Does | Timeout |
|---|---|---|
| pr-title | Validates PR title follows conventional commit format | 5 min |
| commit-messages | Validates all commit messages in the PR | 5 min |
| quality | Typecheck, lint, format check, build | 10 min |
| coverage | Runs tests with coverage enforcement | 10 min |
| integration-tests | Runs integration tests against a real PostgreSQL | 10 min |
| ci-gate | Meta-job: fails if any required job failed | — |
| docker-build | Builds and pushes Docker images to GHCR | 15 min |
The ci-gate job is the branch protection check — it aggregates all required job results into a single pass/fail.
docker-build runs after ci-gate passes and builds three images: toast-api, toast-admin, and toast-docs. Images are tagged with both the branch/PR identifier and the commit SHA.
Pull Request Process
Creating a PR
- Create a feature branch from
main - Make your changes, ensuring
pnpm checkpasses - Push and open a PR — the title must follow conventional commit format
- Wait for CI to pass
- Address reviewer feedback
PR Title Format
PR titles follow the same format as commit messages:
feat(api): add content filter by authorThis is validated by the pr-title CI job using action-semantic-pull-request.
Code Review Expectations
When a reviewer flags a concern:
- Test your assumptions — don't just reason about it, verify it works
- Be careful with patterns — shell globs, regex, and pathspecs behave differently across tools
- "It should work" ≠ "I verified it works" — run the actual command before dismissing feedback
When dismissing a suggestion, explain your reasoning and confirm you tested it.
Requesting Copilot Re-review
After addressing Copilot review comments:
gh api -X POST repos/TryGhost/Toast/pulls/{PR_NUMBER}/requested_reviewers \
-f 'reviewers[]=copilot-pull-request-reviewer[bot]'gh pr edit --add-reviewer Copilot does not reliably trigger a re-review. The API call above is the reliable method.
Test Coverage
New code requires 100% test coverage. The pre-push hook and CI enforce this. See Testing Patterns for:
- Unit test conventions and mocking
- Integration test setup with real PostgreSQL
- Snapshot testing patterns
Preserving Learnings
Discoveries made while working on issues should be captured permanently:
| Size of Learning | Where to Capture It |
|---|---|
| Small | Issue comments or commit message |
| Medium | Update relevant docs or code comments |
| Large | Write an ADR in docs/decisions/ |
Examples worth capturing: why a library was chosen, non-obvious configuration, gotchas, helpful documentation links, and decisions that were considered but rejected.
Infrastructure Changes
When adding environment variables or changing deployment infrastructure:
| Change | Files to Update |
|---|---|
| New environment variable | .env.example, docs/railway-setup.md, docs/patterns/docker.md |
| New build-time variable | Above + relevant Dockerfile (add ARG/ENV) |
| New Railway service | docs/railway-setup.md |
| Docker build changes | docs/patterns/docker.md, relevant Dockerfile |
| GitHub Actions secrets | docs/railway-setup.md (section 16) |