Skip to main content

Branch Promotion Flow

This repo uses a strict dev → staging → main promotion flow. Code only reaches main through a clean chain of promotion PRs (or via hotfix/* once production exists).

Diagram

Rules (enforced two ways)

1. Branch rulesets (UI: Settings → Rules)

Three rulesets, all Active, all enforce on:

BranchPR requiredApprovalsStale reviewsForce-pushBranch deletionBypass
dev≥1dismissAdmin only
staging≥1dismissAdmin only
main≥1dismissAdmin only

2. CI guard (.github/workflows/enforce-promotion-flow.yml)

The guard job in this workflow runs on every PR opened against main or staging:

  • PRs to main must come from staging (or hotfix/*).
  • PRs to staging must come from dev (or hotfix/*).
  • PRs to dev are unrestricted (feature branches).

To make the guard binding, mark guard as a required status check in the main and staging rulesets (Settings → Rules → main/staging → Require status checks → Add guard).

How to promote

# Promote dev -> staging
gh pr create --base staging --head dev --title "promote dev -> staging" --body "Promotion PR."

# Promote staging -> main
gh pr create --base main --head staging --title "promote staging -> main" --body "Promotion PR."

Hotfix path

hotfix/* branches are exempt by design — they're the emergency-patch path for production. They don't exist yet because we don't have a production deployment. Once prod exists, the protocol is:

  1. Branch hotfix/<short-name> off main.
  2. Fix + PR directly into main (and staging, and dev) — three PRs, all from the same hotfix branch.
  3. The CI guard exempts hotfix/* regardless of base.

Why

Previously, main and staging were "protected" but only against force-push and deletion. Anyone with write access could push directly. The rulesets close that gap. The CI guard closes the next failure mode: a PR into main from a feature branch that bypasses staging.

  • Issue #535 — the work that landed this setup
  • PR that landed the CI workflow + this doc