Tern LogoTern
← Back to all posts

Why Every Migration Feels Different (and what to do about it)

TR Jordan
Why Every Migration Feels Different (and what to do about it)

These are the highlights from an episode of Tern Stories. You can watch the full conversation with Ryan Greenberg on YouTube, Spotify, Apple, or wherever you get your podcasts.

You’d think migrations would be repeatable.

Same framework, same upgrade guide—so teams should hit the same issues. But what actually happens is much weirder.

One team hits test failures because their harness relied on internal behavior that changed. Another has to unwind a bespoke data structure used in a dozen places. A third discovers a plugin that never quite worked, but now really doesn’t. It’s the same migration—but everyone’s doing different work.

We’ve seen this over and over—running migrations ourselves, and talking to teams doing the same. The more we saw, the clearer it got: no migration guide covers your system. Migrations surface the weird corners of your stack, the local decisions and long-forgotten workarounds that make your codebase yours.

Which leads to the first big lesson: you can’t plan your way to certainty.

You Can’t Plan Your Way to Certainty

Most migrations don’t fail because someone forgot to read the upgrade guide. They fail because something behaves differently than expected—and the only way to find out is to try it.

You kick the version. Fix a few errors. Run the tests. Click around the app. And pretty quickly, the real work reveals itself.

That’s not a failure of planning. It’s how you learn. Because the information you need rarely lives in one place. Some of it’s in the code. Some shows up only at runtime—like deprecation warnings buried in logs. Some depends on how the app is used: which features matter, which edge cases are reachable. And some of it you only see in production, where APIs, integrations, and real data interact in unpredictable ways.

The fastest way to build confidence isn’t to map everything out in advance—it’s to start doing the work, and let the gaps reveal themselves. That’s what feedback loops are for: spike a branch, skim the diff, run a traffic replay, trigger the edge case you were worried about. You don’t need perfect coverage—you need surface area.

Today, that context-gathering is still mostly human work. But it doesn’t have to be. Tools can help—if they can get access to the same signals engineers use: runtime behavior, production impact, usage patterns. That’s where we see the future heading. But right now, if you want to understand a migration, there’s still no better move than trying it.

The Agentic Future

We kept running into the same insight, no matter who we talked to: the hardest part of a migration isn’t writing the code—it’s deciding what needs to change.

In the episode, Ryan put it simply:

“You want to make the right decision for each place where the code is used—and that’s different everywhere.” - Ryan Greenberg

That’s the real bottleneck. Most of the effort goes into figuring out how the system actually works. Is this usage still valid? Does that dependency need to change? Can we even touch this part of the app without breaking something in prod?

That’s why documentation and upgrade guides only get you so far. They describe the ideal case. But migrations hit the edge cases—the undocumented behaviors, the “temporary” hacks, the real-world patterns your team evolved over time. It’s not that the code is hard to change—it’s that you don’t always know how it should change.

And that’s the opportunity.

Imagine encoding what “better” looks like—once—and then having a way to apply it across the codebase, safely and contextually. Not in one massive PR. Not by interrupting ten teams. But by surfacing the right change in the right place, when it’s needed.

Sometimes that means writing code. Sometimes it means guiding a human. Sometimes it means waiting. But in all cases, it means bringing the decision closer to the moment of execution, with just enough context to move forward confidently.

That’s what it looks like to turn migrations into infrastructure: not just automating the change, but distributing the judgment.


There’s No Shortcut—But There Is Leverage

No two migrations are alike—but the best teams don’t try to make them uniform. They invest in leverage.

They build tight feedback loops. They find ways to observe and test in context. They encode decisions once and apply them broadly—whether that’s through automation, shared tooling, or better in-editor nudges. And they shift the work closer to the moment it matters, where developers have the most clarity and the most context.

That’s the direction we’re moving: not just automating what’s obvious, but supporting the judgment calls that make migrations hard in the first place. Turning migrations from one-off projects into durable systems. Infrastructure, not chaos.

We’re early. But the path is getting clearer.

👀 Watch the full conversation below to hear how we’ve seen these patterns play out—and what we’re learning from the teams doing it best.

Watch the Full Episode

Never miss a post.