Caching the entire context and then switching back to it could cause some really awkward results depending on the other code involved. Passing Job into withContext bypasses normal structured concurrency behavior.
Each call to withContext basically creates a new child Job. withContext(ctx) is creating a Job that is a sibling, not child, of the withContext(Dispatchers.IO) created Job.
I suggest limiting the cached context to just the part you care about:
val ctx = coroutineContext[ContinuationInterceptor]
Good point. I was thinking mostly about using a dispatcher from the outer scope, but I missed the fact that the context is actually much more than that.