The rewrite is usually the wrong instinct
A rewrite promises to trade a known, working system with known problems for an unknown system with unknown problems, while feature work stops. It is occasionally correct and reliably more expensive than the estimate, because the estimate omits everything the old system quietly handles.
Unpleasant is not the same as unfit
Code that is ugly, unfashionable, or written by someone who left is not a business problem. Code that cannot support a change the business needs is. Only the second justifies structural work, and confusing the two is the most common way engineering time gets spent on nothing a customer will notice.
“I don’t like this code” and “this code can’t do what we need” are different findings with very different price tags.
The test that actually separates them
Take the next three things the business genuinely wants. If they are hard because the system’s core model is wrong — the domain has changed shape and the data structures encode the old shape — that is a rewrite case. If they are hard because a few areas are tangled, that is refactoring, and it can be done incrementally while shipping.
Strangle rather than replace
Where a rewrite genuinely is warranted, doing it behind a stable interface and moving traffic piece by piece keeps the business running and lets you stop halfway if the value stops materialising. A clean interface, which is much the same discipline as designing an API others build on, is what makes that possible.
Ask what happens if you do nothing
For a real rewrite case, doing nothing has a describable cost: features that can’t ship, a scaling wall with a date on it, a dependency that loses support. If nobody can articulate that cost, the honest answer is usually that the team is bored, and that is worth solving directly rather than through a nine-month project.
Boredom is a legitimate retention problem. It is a terrible architecture rationale.