Finish the migration
When a new way replaces an old one, the work isn't done until the old way is gone.
When a new way replaces an old one, the migration isn't finished until the old way is deleted. Don't run two lanes indefinitely. Look for the leftovers: compatibility shims, aliases, unused settings, old tables, and doc sentences that only exist to explain the old name.
If the old way can't go yet (a second deploy has to land first, say), record a cleanup task with a clear "ready when" condition in the same change. A code comment isn't a tracker.
Example
Package sql() storage moved from Postgres to per-package SQLite databases in Durable Objects. That happened in two steps. First a release removed every code path that read the old tables. Then, once nothing used them, a separate migration dropped them:
DROP TABLE "org_sql_storage" CASCADE;
DROP TABLE "package_sql_storage" CASCADE; The drop shipped on its own because it can't be undone (see Two-way and one-way doors ). The same pass removed the old cron endpoint, its shared secret, and the Node and Docker deployment.
In your packages
When you replace an export or change a storage key, move the callers and remove the old export in the same version. If stored data has to be converted, do it once with a job, check it, then delete the conversion code.