PR workflow
Branch naming
Section titled “Branch naming”Feature branches follow <type>/<description> or <type>/<ticket-id>-<description>:
feat/business-custom-domainsfix/portal-auth-looprefactor/scheduler-billing-moduleBase all branches off main.
Development loop
Section titled “Development loop”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
CI gates
Section titled “CI gates”A PR cannot merge until:
lint-typecheck-test-buildpasses (oxlint, oxfmt check, tsc, vitest)e2e-syncpasses (full-flow integration tests against Docker infra)
See CI graph for the full pipeline detail.
Pre-PR review (local)
Section titled “Pre-PR review (local)”Before opening a PR, spawn the code-reviewer agent on the diff to catch
issues before Copilot sees them:
# Check your diffgit diff main...HEAD
# Then in Claude Code:# /code-reviewTreat correctness, security, and convention violations as blocking. Surface style / taste findings to the PR description instead of silently “fixing” them.
Addressing review comments
Section titled “Addressing review comments”When you push code that addresses a review comment, you must do both:
- Post a threaded reply on the comment citing the commit SHA and what changed
- Resolve the thread via GitHub (or the GraphQL
resolveReviewThreadmutation)
A reply alone leaves threads unresolved and keeps the PR in “changes requested” state even after the code is fixed.
Pulumi and infrastructure
Section titled “Pulumi and infrastructure”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.
Stacked PRs caution
Section titled “Stacked PRs caution”If you open a PR B that is based on branch A (stacked PR):
- Merging A and deleting its branch via
--delete-branchwill auto-close PR B because its base is gone - Either use
--delete-branch=falseon PR A, or rebase PR B ontomainbefore merging PR A
Post-merge sync
Section titled “Post-merge sync”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:
git stashgit checkout main && git pull --ff-onlygit stash popProduction deployment
Section titled “Production deployment”On every merge to main:
detect-changesidentifies which services changed (Turbo affected + Dockerfile changes)- Affected services build Docker images and push to GHCR
- Railway auto-deploys the new image via the GitHub ↔ Railway integration
pulumi upruns ifinfra/changed
No manual deploy steps. The pipeline is fully automated.