Quick answer: Custom AssetPostprocessor / ScriptedImporter must not call AssetDatabase.SaveAssets, modify .meta files, or trigger imports from within import callbacks. Use ctx.AddObjectToAsset and ctx.DependsOnArtifact only; perform side effects from menu items instead.

Here is how to fix Unity Asset Pipeline V2 stuck reimporting assets in an endless loop. The Editor freezes, progress bar cycles, and saving the project takes forever. The cause is recursion from a custom importer or postprocessor that triggers another import as a side effect.

The Symptom

Importing assets cycles indefinitely. Editor.log fills with import messages for the same files. CPU usage spikes. Cancel does not stop it.

What Causes This

SaveAssets in import. Calling AssetDatabase.SaveAssets inside OnImportAsset triggers a write that re-imports the file.

Modifying .meta in postprocess. Updating the asset’s .meta file from a postprocessor changes the import hash; Unity re-imports.

Cross-asset writes. An importer for A that writes to B causes B’s importer to write to A; loop.

The Fix

Step 1: Keep ScriptedImporter side-effect-free.

using UnityEditor.AssetImporters;
using UnityEngine;

[ScriptedImporter(1, "mydata")]
public class MyImporter : ScriptedImporter
{
    public override void OnImportAsset(AssetImportContext ctx)
    {
        var obj = ScriptableObject.CreateInstance<MyData>();
        // populate obj from file
        ctx.AddObjectToAsset("main", obj);
        ctx.SetMainObject(obj);
        // NO: AssetDatabase.SaveAssets()
        // NO: AssetDatabase.WriteImportSettingsIfDirty()
        // NO: System.IO.File.WriteAllText for meta files
    }
}

Step 2: Move side effects to menu items. Asset post-processing that modifies other assets (generating spritesheets, batch transforms) should be a manual menu item, not an automatic postprocessor.

Step 3: Use OnPostprocessAllAssets correctly.

static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moved, string[] movedFromPaths)
{
    foreach (var p in imported)
    {
        if (p.EndsWith(".png"))
        {
            // Read-only operations OK
            // AssetDatabase.LoadAssetAtPath, etc.
        }
    }
    // Avoid AssetDatabase.SaveAssets here
}

Step 4: Use DependsOnArtifact for clean dependencies.

ctx.DependsOnArtifact(externalArtifactGuid);

Declare dependencies explicitly so AP2 reimports your asset when the dependency changes — without you needing to write to it.

Step 5: Bisect to find the culprit. Disable Editor scripts in chunks. When the loop stops, you know which postprocessor caused it.

“ScriptedImporters describe how to import. They do not write meta files or trigger other imports. Side effects belong in menu items.”

Related Issues

For AssetBundle name updates, see AssetBundle Name. For domain reload phantom overrides, see Phantom Overrides.

No SaveAssets in import. No meta writes. Use DependsOnArtifact. The loop stops.