Decision record: Feature flags over long-lived branches
We prefer merging small changes behind flags to running long-lived branches that collapse into a risky merge.
Context
Long-lived branches feel safer because they hide unfinished work.
In practice, they turn risk into a single event: the merge.
They also erase feedback. While the branch is private, you can’t see performance, production behavior, or real user flows. You’re building in the dark and hoping the integration day goes well.
When a branch diverges for weeks, the merge is large, review is shallow, and rollback becomes ambiguous. If something breaks, it’s unclear whether to roll back code, revert data changes, or “fix forward” under pressure.
We also saw a social cost: teams stop sharing context because “it’s on my branch.” Delivery turns into a surprise.
Decision
Prefer trunk-based work with feature flags.
- Merge small, reviewable changes.
- Keep the flag default-off until the system is observable and reversible.
- Instrument both paths so you can tell what the flag did.
- Treat every flag as a change with an owner, an expiry, and a removal plan.
- Keep flags simple (usually boolean) and short-lived.
Feature flags are not permissions. They are rollout tools. Don’t use them to replace access control; use them to replace risky merges.
If a change cannot be safely hidden behind a flag, label it honestly as a cutover and plan it like a cutover.
Consequences
- We pay a small ongoing cost: flag plumbing, cleanup, and discipline. We also track flag debt (owner + expiry) so flags don’t become permanent.
- We get safer rollouts: the merge is not the moment of truth.
- We get clearer rollback: turning the flag off is a real mitigation, not a hope.
- We get more honest planning: irreversible work is labeled as such.
Feature flags are not a substitute for a plan. They are a tool for making the plan executable.