Skip to main content

Dropping the dev Branch

Per-repo runbook for cutting over a dev + main repo to trunk-based (main only). Follow this in order. Do not skip steps. The rationale is in Branch Strategy.

One repo at a time

Cut one repo over, watch 3–5 deploys, only then start the next. Batching multiplies the blast radius of any misconfigured step.

Scope​

Applies to coniglio, medusa, and farfalla once they all live on GitHub. The order is GitLab β†’ GitHub migration first, drop dev second. Running this runbook on coniglio or medusa ahead of the broader migration buys nothing and creates a second window of churn for the team to track; wait until every active repo is on GitHub before starting any dev cutover (see Migration Guide).

Repos that are already trunk-based (farfalla-integrations, farfalla-https-guard) do not need this runbook; they only need the split-gate update documented in Deploy Approval Pattern.

Preconditions (verify before starting)​

  • git log main..dev --oneline is empty (nothing on dev that isn't on main). If not empty, open and merge a dev β†’ main PR first, then re-check.
  • No long-lived feature branches are based on dev that would require awkward rebases. If any exist, coordinate with their owners before proceeding.
  • criceto or any downstream trigger fires on environment/deploy events (not on a literal branch name). Confirmed for criceto at the time of writing; re-verify if new downstream triggers have been added since.
  • Split-gate pattern already applied per Deploy Approval Pattern. Dropping dev without the split-gate exposes the deadlock risk during the cutover window.

Step-by-step​

1. Announce​

Post in #eng with 24 hours' notice:

Cutover: <repo> is moving to trunk-based (main only) on <date>. Retarget in-flight PRs to main. See docs/github-migration/branch-strategy.md.

2. List and retarget open PRs​

REPO="publicala/<repo>"

# List open PRs still targeting dev
gh pr list --repo $REPO --state open --base dev \
--json number,title,headRefName,author

# Retarget each one
gh pr edit <PR_NUMBER> --repo $REPO --base main

Coordinate with PR authors before retargeting; they may need to rebase onto main if there are conflicts.

Open PRs at time of writing
  • coniglio#13 (Track pagebreak in epubs)
  • medusa#6 (feat(import): support optional file url in VitalSource template)

Both need retargeting before their dev is deleted.

3. Drain dev​

# Open a final dev β†’ main PR to capture anything still on dev
gh pr create --repo $REPO --base main --head dev \
--title "chore: final dev→main before trunk cutover" \
--body "Final sync before dev is deleted. See docs/github-migration/dropping-dev-branch.md."

# Review, merge, verify

After merge, confirm main..dev is empty again.

4. Freeze dev​

Prevent new pushes to dev while the cutover is in flight. Easiest via the GitHub UI under the repo's Settings β†’ Rules β†’ Rulesets (add a "lock" rule for refs/heads/dev), or via API:

gh api repos/$REPO/rulesets -X POST --input - <<'EOF'
{
"name": "lock-dev-during-cutover",
"target": "branch",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["refs/heads/dev"], "exclude": [] } },
"rules": [{ "type": "update" }, { "type": "deletion" }]
}
EOF

The ruleset is temporary; delete it after step 7.

5. Update .github/workflows/*.yml​

Remove all dev references in the workflow. Concrete edits (names may vary slightly per repo):

# before
on:
push:
branches: [dev, main]
pull_request:
branches: [dev]

jobs:
deploy-staging:
if: github.ref == 'refs/heads/dev' && github.event_name == 'push'
...

# after
on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
deploy-staging:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
...

Also:

  • Drop any if: github.base_ref == 'dev' conditionals (PR-preview jobs now fire on all PRs to main).
  • Collapse any branch-variant matrices that distinguished dev from main.
  • Leave deploy-production untouched (it was already gated by environment: production on push-to-main).

Open this as a PR. Request review. Merge when green.

6. Update branch protection​

The org-level main ruleset (#14411941) already protects main; no change there.

The org-level dev ruleset (#15112321) exists solely to keep dev alive against auto-delete. Once every repo has dropped dev, delete the ruleset:

gh api /orgs/publicala/rulesets/15112321 -X DELETE

Until then, it does no harm on repos that no longer have a dev branch.

Per-repo: ensure required status checks on main still match the job names in the updated workflow (build, lint, phpstan, test, etc.).

7. Delete dev on origin​

After the workflow update has landed on main and at least one deploy has succeeded from the new trunk-based flow:

gh api repos/$REPO/git/refs/heads/dev -X DELETE

Also delete the temporary lock ruleset from step 4.

GitHub preserves deleted refs for 90 days

If something goes wrong, dev can be restored via the Branches UI ("Restore") or gh api repos/$REPO/git/refs -X POST -f ref=refs/heads/dev -f sha=<last-sha>. The last known SHA is visible in the repo's branch list for 90 days after deletion.

8. Watch the next 3–5 deploys​

  • Next merge-to-main: verify deploy-staging runs and staging reports healthy.
  • Next PR: verify the staging-review approval gate still appears on the PR checks panel.
  • Next production deploy: verify deploy-production gate still requires Core Team approval.

Any unexpected status in the first handful of deploys means pause and investigate. Do not push through.

9. Tidy up​

  • Update the repo's CLAUDE.md if it mentions dev or the dev β†’ main flow.
  • Update any internal runbooks, onboarding docs, or Slack canvas entries mentioning dev.
  • Update the status row in Branch Strategy.
  • Archive or delete any Linear/ticket tags of the form targets-dev.

Rollback​

If the cutover breaks deploys in a way that can't be quickly patched:

  1. Restore dev from the preserved ref (within 90 days): gh api repos/$REPO/git/refs -X POST -f ref=refs/heads/dev -f sha=<last-known-sha>.
  2. Revert the workflow PR from step 5.
  3. Re-enable the old dev ruleset rules if they were deleted.
  4. Post in #eng what broke so we can fix the runbook.

Per-repo cutover status​

RepoCutover datedev deletedNotes
coniglioplannedPR #13 to retarget
medusaplannedPR #6 to retarget; bundle curl hardening
farfalladeferredWill happen as part of GitLab β†’ GitHub migration
farfalla-integrationsN/AN/AAlready trunk-based
farfalla-https-guardN/AN/AAlready trunk-based

Update this table after each cutover.

X

Graph View