Today Platform Web — Dev Docs
Workflow

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"
    fi

If 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 main aligned, but no urgency
  • Risky change that wants a soak period → leave it on dev for 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 dev

Then 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 SHA

Or 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:

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_ID
  • secrets.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:

  1. Identify the bad commit on main
  2. Open a revert PR against dev (label [REVERT])
  3. Land it through the queue
  4. Run sync-main.yml again

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:

  1. Surface a decision point. Every prod deploy is an explicit "yes ship this now" rather than a passive consequence of dev landing.
  2. 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.

On this page