The migration that never ends
Every long-running migration stalls at the same place, for the same reason, and it is not technical.

Ask any engineer at a company older than five years whether they have a migration in flight, and the answer is yes. Ask how long it has been running, and watch the pause before they answer.
The pattern is consistent enough to be a law. A migration moves fast to about 80 percent, then slows, then stops, and the remaining 20 percent stays in place for years while both systems are maintained in parallel at roughly double the cost of either.
Why it stalls where it stalls
The easy 80 percent is easy because those call sites were normal. They used the documented interface in the expected way, so a mechanical transformation moves them.
What is left is everything unusual: the caller that depends on the old system’s undocumented ordering guarantee, the batch job someone wrote in a different language, the integration owned by a team that has since dissolved, and the one customer whose contract specifically references the old behaviour.
None of these are hard in the sense of requiring clever code. They are hard in requiring decisions that cross team boundaries, and each one needs a different person’s agreement.
The last ten percent of a migration is not engineering work. It is a series of negotiations that happen to produce diffs.
The thing that makes it permanent
Here is the mechanism that turns a stall into a permanent state. Once the new system handles the common case, the pain of the old system stops being felt by the people with the authority to prioritise finishing.
The migration was funded because the old system hurt. At 80 percent, it only hurts in the corners, and the corners belong to teams whose roadmaps were set elsewhere. So the work becomes nobody’s priority while remaining everybody’s tax, and that configuration is stable. It can persist indefinitely.
What works
Two things, neither of them technical.
Set a deletion date before you start, and make it the deliverable. Not “migrate to the new system” but “delete the old system by Q3.” The framing sounds like a semantic difference and behaves like a completely different project, because a deletion date makes the stragglers visible and dated instead of indefinitely deferred.
Make the old path progressively worse on purpose. Log a warning, then a loud warning, then add latency, then break it in staging. This feels hostile and is mostly just honest: it moves the cost of not migrating onto the people who have not migrated, which is where it belongs and where it was never going to arrive on its own.


