{"record":{"id":"78a4fb897c6a6d4e","repo":"Unity-Technologies/UnityCsReference","slug":"cannot-call-reportassetchanged-outside-of-the-onas","errorCode":null,"errorMessage":"Cannot call ReportAssetChanged outside of the OnAssetsModified callback","messagePattern":"Cannot call ReportAssetChanged outside of the OnAssetsModified callback","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"Editor/Mono/AssetDatabase/AssetsModifiedProcessor.cs","lineNumber":18,"sourceCode":"// Unity C# reference source\n// Copyright (c) Unity Technologies. For terms of use, see\n// https://unity3d.com/legal/licenses/Unity_Reference_Only_License\n\nusing System;\nusing System.Collections.Generic;\nusing UnityEditor.Profiling;\n\nnamespace UnityEditor.Experimental\n{\n    public abstract class AssetsModifiedProcessor\n    {\n        public HashSet<string> assetsReportedChanged { get; set; }\n\n        protected void ReportAssetChanged(string assetChanged)\n        {\n            if (assetsReportedChanged == null)\n                throw new InvalidOperationException(\"Cannot call ReportAssetChanged outside of the OnAssetsModified callback\");\n\n            assetsReportedChanged.Add(assetChanged);\n        }\n\n        //Note: changedAssets including added and moved assets may be a usability issue. Review before making public.\n        ///<summary>Fired when the [[AssetDatabase]] detects Asset changes before any Assets are imported.</summary>\n        ///<param name=\"changedAssets\">Paths to the Assets whose file contents have changed. Includes all added and moved Assets.</param>\n        ///<param name=\"addedAssets\">Paths to added Assets.</param>\n        ///<param name=\"deletedAssets\">Paths to deleted Assets.</param>\n        ///<param name=\"movedAssets\">Array of AssetMoveInfo that contains the previous and current location of any moved Assets.</param>\n        ///<description> An Asset will only be reported moved if its .meta file is moved as well.</description>\n        protected abstract void OnAssetsModified(string[] changedAssets, string[] addedAssets, string[] deletedAssets, AssetMoveInfo[] movedAssets);\n\n        internal void Internal_OnAssetsModified(string[] changedAssets, string[] addedAssets, string[] deletedAssets, AssetMoveInfo[] movedAssets)\n        {\n            var type = GetType();\n            using (new EditorPerformanceMarker($\"{type.Name}.{nameof(OnAssetsModified)}\", type).Auto())\n            {","sourceCodeStart":1,"sourceCodeEnd":36,"githubUrl":"https://github.com/Unity-Technologies/UnityCsReference/blob/225b0fbdb57cc17d094e8056b71f8314aba56f73/Editor/Mono/AssetDatabase/AssetsModifiedProcessor.cs#L1-L36","documentation":"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.'","triggerScenarios":"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.","commonSituations":"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.","solutions":["Only call ReportAssetChanged inside your OnAssetsModified override, never from other methods.","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.","Check the experimental API's lifecycle: ensure you are subclassing correctly and Unity has initialized assetsReportedChanged before your code runs."],"exampleFix":"// before\nprotected override void OnAssetsModified(string[] changed, string[] added, string[] deleted, AssetMoveInfo[] moved)\n{\n    // ...\n}\n\nvoid SomeOtherMethod()\n{\n    ReportAssetChanged(myAsset); // throws: not in callback\n}\n\n// after\nprotected override void OnAssetsModified(string[] changed, string[] added, string[] deleted, AssetMoveInfo[] moved)\n{\n    foreach (var a in changed)\n        ReportAssetChanged(a); // correct: inside callback\n}\n// never call ReportAssetChanged elsewhere","handlingStrategy":"validation","validationCode":"// Only valid inside OnAssetsModified override\nprotected override void OnAssetsModified(string[] changed, string[] added, string[] deleted, AssetMoveInfo[] moved)\n{\n    foreach (var a in changed)\n        ReportAssetChanged(a);\n}","typeGuard":"// Cannot type-guard runtime lifecycle; guard by call-site convention\nstatic bool IsInAssetsModifiedCallback(AssetsModifiedProcessor p) => p.assetsReportedChanged != null;","tryCatchPattern":"try { ReportAssetChanged(asset); }\ncatch (InvalidOperationException)\n{ Debug.LogWarning(\"ReportAssetChanged called outside OnAssetsModified — ignored\"); }","preventionTips":["Only call ReportAssetChanged inside OnAssetsModified override","Never call it from constructors, OnEnable, delayCall, or other callbacks","If deferring work, collect paths in OnAssetsModified and process elsewhere without calling ReportAssetChanged"],"tags":["unity","csharp","asset-pipeline","experimental-api","invalid-operation","lifecycle"],"backgroundTag":null,"analyzedSha":"225b0fbdb57cc17d094e8056b71f8314aba56f73","analyzedAt":"2026-08-13T19:07:19.849Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}