Quick answer: If you set allowSceneActivation = false, the async operation pauses at progress 0.9 and the completed callback never fires. Set allowSceneActivation = true when you are ready to activate the scene, and the callback will trigger.
Here is how to fix async scene loading callbacks not firing in Unity. You called SceneManager.LoadSceneAsync, subscribed to the completed event, and your handler never executes. Your loading bar fills to 90% and freezes. The scene never appears, and neither error messages nor warnings show up in the console. This is one of the most confusing behaviors in Unity’s scene management because the API is working exactly as designed — it just was not designed the way most developers expect.
The Symptom
You start an async scene load, typically in a coroutine or with a callback. You display a progress bar based on asyncOperation.progress. The bar fills to approximately 90% and stops. The completed callback never fires. The new scene never appears. If you check asyncOperation.isDone, it remains false forever.
In some cases, the loading screen works fine during development (small test scenes load instantly), but breaks in production builds where scenes are large enough to expose the two-phase loading behavior.
What Causes This
1. allowSceneActivation is false. Unity splits async loading into two phases. Phase one (progress 0.0 to 0.9) loads all scene data into memory. Phase two (progress 0.9 to 1.0) activates the scene — calls Awake, enables objects, and switches the active scene. When you set allowSceneActivation = false, Unity completes phase one and then waits indefinitely for you to set it back to true. The completed callback only fires after activation, so it never triggers.
2. Subscribing to completed too late. If the scene is small, the operation may complete within the same frame it was started. If you do other work between calling LoadSceneAsync and subscribing to completed, the event may have already fired by the time you attach your handler.
3. The loading object is destroyed. If the MonoBehaviour that owns the coroutine or callback is on a GameObject in the current scene, and the scene load replaces that scene, the callback target gets destroyed before it can be called. The operation completes, but the receiver no longer exists.
The Fix
Step 1: Handle the allowSceneActivation pause correctly.
private IEnumerator LoadSceneWithProgress(string sceneName)
{
AsyncOperation op = SceneManager.LoadSceneAsync(sceneName);
op.allowSceneActivation = false;
while (!op.isDone)
{
// Progress goes from 0 to 0.9 during loading
// Map 0-0.9 to 0-1 for the loading bar
float displayProgress = Mathf.Clamp01(op.progress / 0.9f);
loadingBar.fillAmount = displayProgress;
// When loading phase is done (progress >= 0.9)
if (op.progress >= 0.9f)
{
loadingText.text = "Press any key to continue";
if (Input.anyKeyDown)
{
op.allowSceneActivation = true;
}
}
yield return null;
}
// This line only runs after activation completes
Debug.Log("Scene fully loaded and activated");
}
Step 2: Subscribe to completed immediately.
void StartLoading()
{
AsyncOperation op = SceneManager.LoadSceneAsync("GameScene");
// Subscribe on the very next line — do not defer
op.completed += OnSceneLoaded;
}
void OnSceneLoaded(AsyncOperation op)
{
Debug.Log("Scene loaded and activated");
}
Step 3: Put your loading manager on a persistent object. If the loading screen is in a separate scene or on a DontDestroyOnLoad object, the callback target survives the scene transition. Otherwise, the scene load destroys the very object that is waiting for the callback:
void Awake()
{
DontDestroyOnLoad(gameObject);
}
“The 0.9 pause is not a bug. Unity is telling you: the data is loaded, but I have not activated it yet. You have to explicitly let it go. Treat 0.9 as ‘ready’ and 1.0 as ‘done.’”
Why This Works
Unity’s async scene loading is a two-phase operation by design. The first phase (loading) can happen in the background without disrupting the current scene. The second phase (activation) is destructive — it calls Awake on new objects, potentially unloads the old scene, and triggers all initialization logic. By splitting these, Unity gives you a window to display loading screens, preload assets, or wait for player input before the scene swap occurs.
The completed event is tied to isDone, which only becomes true after both phases finish. When allowSceneActivation is false, isDone stays false at 0.9, and completed never fires. This is consistent but unintuitive for developers who expect “completed” to mean “data is loaded.”
Related Issues
If your async loading causes frame hitches despite being asynchronous, see Fix: Unity Async Await Freezing the Main Thread for proper async patterns that avoid blocking the main thread during activation.
If you are loading scenes additively and the new scene’s lighting is wrong, check Fix: Unity HDRP Volume Profile Not Applying for volume priority issues in multi-scene setups.
progress == 0.9 means loaded. allowSceneActivation = true means go. completed fires after both.