← Blog

August 22, 2026 · Steve Johnson

A Feature Flag That Outlived the Migration It Was Built For

A feature flag named for a payment-provider migration that finished two years ago was still being checked on every checkout request, still had both code paths live, and was still occasionally toggled by a script nobody could fully explain. The migration it was named after was long done. The flag had quietly become permanent load-bearing infrastructure for a decision that was supposed to have been temporary.

A flag is a promise to make a decision later

Every feature flag starts with an implicit deadline: it exists to let a team ship a change safely, gather confidence, and then remove the flag along with whichever code path lost. That deadline is almost never written down anywhere the system enforces, which means it depends entirely on someone remembering to come back and finish the job after the urgency that motivated the flag in the first place has passed. The migration succeeded. The team moved on to the next priority. The flag, with no forcing function attached to its removal, simply stayed.

Two years later, both code paths were still being maintained, in the loose sense that neither one was allowed to actively break, without anyone treating either as the primary path deserving real investment. New engineers encountering the flag for the first time had no way to know, from the code alone, that one branch was the settled outcome and the other was dead weight — the flag gave both paths equal apparent legitimacy, when in reality one of them had been correct for two years and the other should have been deleted the week the migration finished.

The cost isn't the flag, it's what depends on not resolving it

A single old flag checked in one place is a minor tax. What actually accumulates cost is everything built afterward that has to account for the flag's existence without understanding its history — a new engineer adding a feature to checkout has to reason about both code paths, not because both paths matter, but because the flag's presence signals ambiguity that no longer exists in reality. Tests have to cover both branches. Incident response has to consider whether an issue is specific to one path or the other. All of that ongoing cost exists purely because a two-year-old decision was never formally closed out, not because the decision itself was ever actually in doubt.

That's the pattern worth naming: a stale flag doesn't cost much by sitting there unused. It costs by forcing every future change near it to maintain ambiguity about something that was actually resolved a long time ago.

What actually gets a flag removed

Reminding people to clean up old flags doesn't work reliably, for the same reason reminding people to update runbooks doesn't work — it competes against work with a visible deadline and loses. What worked here was mechanical rather than cultural: an automated report, run monthly, listing every flag by age and by how uniform its evaluation has been over the trailing ninety days. A flag that's resolved to the same value for ninety straight days isn't actively deciding anything anymore; it's a static configuration wearing a flag's clothing, and that's a specific, checkable condition rather than a vague "this seems old" judgment call.

That report turned "someone should clean up old flags eventually" into a specific, recurring list of named flags with evidence attached, assigned to whoever owned the surrounding code — which is what actually got this one, and four others discovered alongside it, removed within a quarter after sitting untouched for years. The flag wasn't hard to remove once someone looked at it. The entire cost was that nothing in the system ever forced anyone to look.