dev → main sync
How `sync-main.yml` fast-forwards `main` to `dev` on manual dispatch — the gate that turns a `dev`-merged PR into a production deploy.
dev is the development branch. PRs merge here through the queue. main is
the production branch. main never receives a PR directly — its only
source of new commits is dev, via the sync-main.yml workflow.
The workflow
.github/workflows/sync-main.yml
is manual dispatch only (workflow_dispatch:). It does one thing:
- name: Fast-forward main
run: |
git fetch origin main
if git merge-base --is-ancestor origin/main origin/dev; then
git push origin origin/dev:refs/heads/main
else
echo "::warning::main has diverged from dev — skipping fast-forward"
fiIf main is an ancestor of dev (the common case — clean linear history),
main advances to whatever dev is. If main has commits that aren't in
dev (shouldn't happen under normal operation), the workflow warns and
skips. The non-destructive failure mode is intentional.
When to run it
After a PR merges to dev, decide whether the change should ship to prod:
- Bug fix users are hitting → sync immediately
- Feature behind a flag → sync at your convenience
- Internal-only change (CI, dev-docs, scripts) → sync to keep
mainaligned, but no urgency - Risky change that wants a soak period → leave it on
devfor 24-48h first
There's no automated batching window. The team's habit is to sync 1-3 times per day during active development.
How to trigger
gh workflow run sync-main.yml --ref devThen wait and verify:
gh run list --workflow=sync-main.yml --limit 1
# wait until "completed"
git ls-remote origin main dev
# both refs should print the same SHAOr from the GitHub UI: Actions → "Sync main from dev" → Run workflow → pick
dev.
What happens after sync
Pushing to main triggers the production deploy workflows:
.github/workflows/deploy-web.ymldetects the push, builds, and shipsapps/webto the production Vercel environment..github/workflows/deploy-admin.ymldoes the same forapps/admin..github/workflows/deploy-docs.ymlshipsdesign-system/docs.- This site's deploy workflow (
deploy-dev-docs.yml) ships if anything underapps/dev-docs/changed in the sync.
Each deploy waits for its required CI checks before promoting (see Deployment for the gate logic).
Auth + tokens
The workflow uses a GitHub App token (actions/create-github-app-token@v3)
rather than GITHUB_TOKEN because branch-protection rules on main forbid
pushes from generic GITHUB_TOKEN. The App is configured with:
vars.TODAY_PLATFORM_CI_CLIENT_IDsecrets.TODAY_PLATFORM_CI_APP_PRIVATE_KEY
Both live at the org / repo level. The App identity is the only thing
trusted to push to main.
Skipping or rolling back
To skip a sync for a specific dev commit: there's no per-commit mechanism. The trigger is "advance main to dev's current HEAD." If you want to ship part of dev but not the latest commit, you'd have to revert the later commits on dev first (PR through queue, normal flow), then sync.
To roll back a production deploy after sync:
- Identify the bad commit on
main - Open a revert PR against
dev(label[REVERT]) - Land it through the queue
- Run
sync-main.ymlagain
The revert flows through the same pipeline as any other change, which keeps production rollbacks as boring and auditable as forward changes.
Why not auto-sync
A cron-style auto-sync would shrink the dev-to-prod gap, but the team has deliberately kept the manual gate for two reasons:
- Surface a decision point. Every prod deploy is an explicit "yes ship this now" rather than a passive consequence of dev landing.
- Catch unintended dev landings. Rare but real — a PR that should have waited for a feature flag flip can be caught before it goes live by the "do we sync yet?" check.
If the manual cost ever becomes the bottleneck, the conversation is about adding a soak-time-aware auto-sync, not about removing the gate entirely.
Merge queue
How GitHub's merge queue interacts with `gh pr merge` on this repo. The `UNSTABLE` vs `CLEAN` vs `BLOCKED` states, what cancels auto-merge, and the speculative-branch double-run.
CI affected scope
How `scripts/affected.ts` computes which packages a PR actually touches and how that scopes typecheck / test / lint / visual-regression on CI.