Unity-Technologies/UnityCsReference · error · InvalidOperationException

Cannot call ReportAssetChanged outside of the OnAssetsModifi

Error message

Cannot call ReportAssetChanged outside of the OnAssetsModified callback

What it means

AssetsModifiedProcessor.ReportAssetChanged is only valid inside the OnAssetsModified virtual callback. The protected method throws InvalidOperationException when assetsReportedChanged is null, because Unity sets that HashSet only during the callback dispatch — calling ReportAssetChanged at any other time means you are outside the valid window. The null check on the property is the sentinel for 'not currently in callback context.'

Source

Thrown at Editor/Mono/AssetDatabase/AssetsModifiedProcessor.cs:18

// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System;
using System.Collections.Generic;
using UnityEditor.Profiling;

namespace UnityEditor.Experimental
{
    public abstract class AssetsModifiedProcessor
    {
        public HashSet<string> assetsReportedChanged { get; set; }

        protected void ReportAssetChanged(string assetChanged)
        {
            if (assetsReportedChanged == null)
                throw new InvalidOperationException("Cannot call ReportAssetChanged outside of the OnAssetsModified callback");

            assetsReportedChanged.Add(assetChanged);
        }

        //Note: changedAssets including added and moved assets may be a usability issue. Review before making public.
        ///<summary>Fired when the [[AssetDatabase]] detects Asset changes before any Assets are imported.</summary>
        ///<param name="changedAssets">Paths to the Assets whose file contents have changed. Includes all added and moved Assets.</param>
        ///<param name="addedAssets">Paths to added Assets.</param>
        ///<param name="deletedAssets">Paths to deleted Assets.</param>
        ///<param name="movedAssets">Array of AssetMoveInfo that contains the previous and current location of any moved Assets.</param>
        ///<description> An Asset will only be reported moved if its .meta file is moved as well.</description>
        protected abstract void OnAssetsModified(string[] changedAssets, string[] addedAssets, string[] deletedAssets, AssetMoveInfo[] movedAssets);

        internal void Internal_OnAssetsModified(string[] changedAssets, string[] addedAssets, string[] deletedAssets, AssetMoveInfo[] movedAssets)
        {
            var type = GetType();
            using (new EditorPerformanceMarker($"{type.Name}.{nameof(OnAssetsModified)}", type).Auto())
            {

View on GitHub (pinned to 225b0fbdb5)

Solutions

  1. Only call ReportAssetChanged inside your OnAssetsModified override, never from other methods.
  2. If you need to defer processing, collect paths in OnAssetsModified and process them after via EditorApplication.delayCall — do not call ReportAssetChanged from the deferred callback.
  3. Check the experimental API's lifecycle: ensure you are subclassing correctly and Unity has initialized assetsReportedChanged before your code runs.

Example fix

// before
protected override void OnAssetsModified(string[] changed, string[] added, string[] deleted, AssetMoveInfo[] moved)
{
    // ...
}

void SomeOtherMethod()
{
    ReportAssetChanged(myAsset); // throws: not in callback
}

// after
protected override void OnAssetsModified(string[] changed, string[] added, string[] deleted, AssetMoveInfo[] moved)
{
    foreach (var a in changed)
        ReportAssetChanged(a); // correct: inside callback
}
// never call ReportAssetChanged elsewhere
Defensive patterns

Strategy: validation

Validate before calling

// Only valid inside OnAssetsModified override
protected override void OnAssetsModified(string[] changed, string[] added, string[] deleted, AssetMoveInfo[] moved)
{
    foreach (var a in changed)
        ReportAssetChanged(a);
}

Type guard

// Cannot type-guard runtime lifecycle; guard by call-site convention
static bool IsInAssetsModifiedCallback(AssetsModifiedProcessor p) => p.assetsReportedChanged != null;

Try / catch

try { ReportAssetChanged(asset); }
catch (InvalidOperationException)
{ Debug.LogWarning("ReportAssetChanged called outside OnAssetsModified — ignored"); }

Prevention

When it happens

Trigger: Calling ReportAssetChanged from a constructor, from another virtual method, from a timer/coroutine, or from OnAssetsModified after the base class has already finalized the set. Also triggered if a subclass calls it in an overridden method that runs before Unity initializes the HashSet.

Common situations: Subclassing AssetsModifiedProcessor (an experimental API) to receive pre-import asset change notifications, and incorrectly calling ReportAssetChanged outside the OnAssetsModified override — e.g., from OnEnable, from a deferred delegate, or from a separate thread.

Related errors


AI-assisted analysis of Unity-Technologies/UnityCsReference@225b0fbdb5 (2026-08-13). Data as JSON: /api/errors/78a4fb897c6a6d4e. Report an issue: GitHub.