{"record":{"id":"975609e2932cfbe9","repo":"stride3d/stride","slug":"one-of-the-asset-does-not-match-the-directory-hierarchy","errorCode":null,"errorMessage":"One of the asset does not match the directory hierarchy.","messagePattern":"One of the asset does not match the directory hierarchy\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/editor/Stride.Core.Assets.Editor/ViewModel/AssetCollectionViewModel.cs","lineNumber":1069,"sourceCode":"            }\n\n            return collection;\n        }\n\n        /// <summary>\n        /// Consistency check. Makes sure <paramref name=\"assets\"/> are indeed inside the given <paramref name=\"directory\"/>.\n        /// </summary>\n        /// <param name=\"assets\"></param>\n        /// <param name=\"directory\"></param>\n        private static void EnsureDirectoryHierarchy(IEnumerable<AssetViewModel> assets, DirectoryBaseViewModel directory)\n        {\n            foreach (var asset in assets)\n            {\n                // Locations in namespaced packages are rooted /Namespace/...; the directory tree is bare\n                var location = asset.AssetItem.UnqualifiedUrl;\n                if (location.HasDirectory && (directory.Parent == null || !location.FullPath.StartsWith(directory.Parent.Path, StringComparison.Ordinal)))\n                {\n                    throw new InvalidOperationException(\"One of the asset does not match the directory hierarchy.\");\n                }\n            }\n        }\n\n        /// <summary>\n        /// Actually writes the assets to the clipboard.\n        /// </summary>\n        /// <param name=\"assetsToWrite\"></param>\n        /// <returns></returns>\n        private bool WriteToClipboard(IEnumerable<IGrouping<string, AssetViewModel>> assetsToWrite)\n        {\n            var assetCollection = new List<AssetItem>();\n            assetCollection.AddRange(assetsToWrite.SelectMany(\n                    grp => grp.Select(a => new AssetItem(UPath.Combine<UFile>(grp.Key, a.AssetItem.Location.GetFileNameWithoutExtension()), a.AssetItem.Asset))));\n            try\n            {\n                var text = ServiceProvider.TryGet<ICopyPasteService>()?.CopyMultipleAssets(assetCollection);\n                if (string.IsNullOrEmpty(text))","sourceCodeStart":1051,"sourceCodeEnd":1087,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/editor/Stride.Core.Assets.Editor/ViewModel/AssetCollectionViewModel.cs#L1051-L1087","documentation":"When performing a clipboard/selection operation on a set of assets relative to a target directory, the editor validates that each asset's location actually lives under the expected parent path. Locations in namespaced packages are rooted at /Namespace/... while the directory tree is bare, so a mismatch means one asset does not belong to the directory hierarchy being operated on and the operation is aborted.","triggerScenarios":"Copy/cut/clipboard operations (or any API passing a directory plus a set of assets) where an asset's UnqualifiedUrl path does not start with directory.Parent.Path — e.g. mixing assets from a different package or namespace, or passing the wrong directory argument.","commonSituations":"Editor plugins or scripts copying assets across packages/namespaces; passing a root-level directory (Parent == null) with non-rooted assets; mixing selection contents from multiple packages.","solutions":["Verify all selected assets belong to the same package/directory subtree as the target directory before the operation.","Use the asset's UnqualifiedUrl (not the namespaced FullPath) when doing your own hierarchy checks.","For root-level targets, handle directories with Parent == null separately instead of passing them with non-rooted assets."],"exampleFix":"// before\nvm.CopyToClipboard(selectedAssets, targetDirectory);\n// after\nvar expected = targetDirectory.Parent?.Path;\nif (selectedAssets.All(a => a.AssetItem.UnqualifiedUrl.FullPath.StartsWith(expected, StringComparison.Ordinal)))\n    vm.CopyToClipboard(selectedAssets, targetDirectory);","handlingStrategy":"validation","validationCode":"var parentPath = directory.Parent?.Path;\nbool allMatch = assets.All(a => parentPath != null && a.AssetItem.UnqualifiedUrl.FullPath.StartsWith(parentPath, StringComparison.Ordinal));\nif (!allMatch) throw new InvalidOperationException(\"Selection spans directories/packages; split the operation\");","typeGuard":null,"tryCatchPattern":"try { vm.CopyToClipboard(assets, directory); }\ncatch (InvalidOperationException ex) { logger.Warn(ex, \"Asset does not match directory hierarchy; check package/namespace\"); }","preventionTips":["Keep selections within one package and directory subtree","Compare paths using UnqualifiedUrl, not the namespaced FullPath","Watch for null Parent when targeting root directories"],"tags":["assets","directory-hierarchy","clipboard","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}