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.
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 --onelineis empty (nothing ondevthat isn't onmain). If not empty, open and merge adevβmainPR first, then re-check. - No long-lived feature branches are based on
devthat 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
devwithout 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 (mainonly) on<date>. Retarget in-flight PRs tomain. 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.
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 tomain). - Collapse any branch-variant matrices that distinguished
devfrommain. - Leave
deploy-productionuntouched (it was already gated byenvironment: productionon 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.
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-stagingruns and staging reports healthy. - Next PR: verify the
staging-reviewapproval gate still appears on the PR checks panel. - Next production deploy: verify
deploy-productiongate 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.mdif it mentionsdevor 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:
- Restore
devfrom the preserved ref (within 90 days):gh api repos/$REPO/git/refs -X POST -f ref=refs/heads/dev -f sha=<last-known-sha>. - Revert the workflow PR from step 5.
- Re-enable the old
devruleset rules if they were deleted. - Post in
#engwhat broke so we can fix the runbook.
Per-repo cutover statusβ
| Repo | Cutover date | dev deleted | Notes |
|---|---|---|---|
coniglio | planned | PR #13 to retarget | |
medusa | planned | PR #6 to retarget; bundle curl hardening | |
farfalla | deferred | Will happen as part of GitLab β GitHub migration | |
farfalla-integrations | N/A | N/A | Already trunk-based |
farfalla-https-guard | N/A | N/A | Already trunk-based |
Update this table after each cutover.
Relatedβ
- Branch Strategy is the "why".
- Deploy Approval Pattern is the "how deploys work on trunk".
- Migration Guide is the full GitLab β GitHub checklist (
farfallawill use it).