Quick answer: Unity coroutines surviving a scene unload because they were started on DontDestroyOnLoad objects? Track per-coroutine handles and stop them in OnSceneUnloaded.
A timer coroutine started on a persistent manager keeps ticking after a scene change, firing callbacks against destroyed objects.
Hold the handle
Coroutine c = StartCoroutine(Loop());
if (c != null) StopCoroutine(c);Without the handle you can't selectively stop one. StopAllCoroutines nukes too much.
Hook scene unload
Subscribe to SceneManager.sceneUnloaded on the persistent manager and stop scene-scoped coroutines there. Coroutines started on destroyed objects auto-stop; ones on DontDestroyOnLoad don't.
Prefer async/await
Modern Unity supports await with UniTask or built-in awaitables. Cancellation tokens give you composable cleanup that coroutines never had.
“Coroutines outlive their callers when the caller isn't actually their owner.”
Move ticking managers off the DontDestroyOnLoad pile. Most timers belong to scenes; persistence is a separate concern.