Skip to content

PR workflow

Feature branches follow <type>/<description> or <type>/<ticket-id>-<description>:

feat/business-custom-domains
fix/portal-auth-loop
refactor/scheduler-billing-module

Base all branches off main.

flowchart TD
    Branch[Create feature branch from main]
    Code[Write code]
    Lint[pnpm oxfmt + oxlint locally]
    Test[pnpm test\nunit tests]
    Push[git push → open PR]
    CI[CI: lint + typecheck + build + e2e-sync]
    Review[Code review\nhuman + Copilot]
    Merge[Merge to main]
    Deploy[deploy-production.yml\nautomatically builds + deploys]
    Pulumi[pulumi.yml\nautomatically applies infra if infra/ changed]

    Branch --> Code
    Code --> Lint
    Lint --> Test
    Test --> Push
    Push --> CI
    CI -->|pass| Review
    CI -->|fail| Code
    Review -->|approved| Merge
    Review -->|changes requested| Code
    Merge --> Deploy
    Merge --> Pulumi

A PR cannot merge until:

  1. lint-typecheck-test-build passes (oxlint, oxfmt check, tsc, vitest)
  2. e2e-sync passes (full-flow integration tests against Docker infra)

See CI graph for the full pipeline detail.

Before opening a PR, spawn the code-reviewer agent on the diff to catch issues before Copilot sees them:

Terminal window
# Check your diff
git diff main...HEAD
# Then in Claude Code:
# /code-review

Treat correctness, security, and convention violations as blocking. Surface style / taste findings to the PR description instead of silently “fixing” them.

When you push code that addresses a review comment, you must do both:

  1. Post a threaded reply on the comment citing the commit SHA and what changed
  2. Resolve the thread via GitHub (or the GraphQL resolveReviewThread mutation)

A reply alone leaves threads unresolved and keeps the PR in “changes requested” state even after the code is fixed.

Changes to infra/** trigger the pulumi.yml workflow:

  • On PR: pulumi preview — posts the plan as a PR comment
  • On merge to main: pulumi up --stack stage — applies automatically

Never run pulumi up manually. The CI pipeline is the single writer.

If you open a PR B that is based on branch A (stacked PR):

  • Merging A and deleting its branch via --delete-branch will auto-close PR B because its base is gone
  • Either use --delete-branch=false on PR A, or rebase PR B onto main before merging PR A

gh pr merge --delete-branch runs a local fast-forward after the remote merge. If you have unstaged changes, the local sync fails (but the remote merge already succeeded). To recover:

Terminal window
git stash
git checkout main && git pull --ff-only
git stash pop

On every merge to main:

  1. detect-changes identifies which services changed (Turbo affected + Dockerfile changes)
  2. Affected services build Docker images and push to GHCR
  3. Railway auto-deploys the new image via the GitHub ↔ Railway integration
  4. pulumi up runs if infra/ changed

No manual deploy steps. The pipeline is fully automated.