Deleting UAssets from Commandlet

I have a class that extends UCommandlet and sets IsEditor=true.

Within the commandlet, three major steps are run:

  1. Rename any uassets that have changed - AssetToolsModule.Get().RenameAssets(AssetsToRename);
  2. Cleanup Redirectors - AssetToolsModule.Get().FixupReferencers(Redirectors, false, ERedirectFixupMode::DeleteFixedUpRedirectors);
  3. Delete any stale assets that have no references ObjectTools::DeleteAssets(AssetsToDelete, false);

I gather only assets that have no references for deletion.

Calling any of the delete functions in UE fails because they all attempt to interact with a slate window.

I am supplying -unattended -nopause -nullrhi -nosplash -nographics on the commandline.

See the screenshot for one of the crash dumps.

Is there any way to make this work?

Also will the Rename and FixupRedirectors functions work, since they both always try to open a dialogue box?

Thanks!

DeleteObjectsCrash.png(42.9 KB)

Steps to Reproduce

Call any of the following delete functions within code executing from a commandlet:

ObjectTools::ForceDeleteObjects(ObjectsToDelete, IsRunningCommandlet() == false)
ObjectTools::DeleteObjects(ObjectsToDelete, IsRunningCommandlet() == false);
ObjectTools::DeleteAssets(AssetsToDelete, IsRunningCommandlet() == false);
IFileManager::Get().Delete(*Paths);
UEditorAssetLibrary::DeleteAsset(AssetName);

Observe that the commandlet crashes. Each of these functions tries to interact with a slate window, regardless of setting the dialogue box boolean to false.

Hi there,

Thank you for providing the information and detailed reproduction steps.

I was unable to reproduce the issue using the provided steps in UE 5.6. In my testing, DeleteAssets worked as expected. This suggests the crash may be related to the specific project setup or particular assets involved.

In this case, the functions in ObjectTools or AssetTools operate at the UI layer. These utilities wrap lower-level systems and may attempt to display confirmation dialogs, which can lead to failures when running in a commandlet or unattended environment.

To help prevent unintended Slate modal interactions, you may consider explicitly setting:

GIsRunningUnattendedScript = true

Additionally, cleaning the Intermediate folder may help, especially if there are duplicated generated or renamed assets or stale redirectors.

If this case allows, reviewing UResavePackagesCommandlet may also be beneficial. It demonstrates how to operate at the package level in a commandlet-safe manner.

If you’re able to share additional context about this case, or a minimal repro project, it would greatly assist us in investigating the issue further.

Best regards,

Henry Liu

Hi,

Thank you for the update.

The test project I created includes a commandlet that calls ObjectTools::DeleteAssets. As shown in the log below,

DeleteAssets completes successfully, and CloseAllEditorsForAsset is also executed without any issues.

The crash appears to be related to an invalid or dirty memory access. This type of issue can be subtle and may be specific to your project’s implementation or to certain assets within it.

Since there is limited context of the implementation, it’s difficult to determine the exact cause on your end. If you could provide additional details about this case or share a minimal reproducible project, that would greatly help us investigate the issue further.

Best regards,

Henry Liu

Hi [mention removed]​ ,

Thank you for the thorough response!

DeleteAssets works normally (when running the editor), but it does fail when invoked from a commandlet.

Setting “GIsRunningUnattendedScript = true” does not seem to make any difference. I also deleted the Intermediate folder.

Here is the snippet of code:

for (const FAssetData& Asset : AllUAssets)
{
	UMyAsset* MyAsset = Cast<UMyAsset>(Asset.GetAsset());
 
	if (MyAsset == nullptr)
		continue;
 
	if (MyAsset->AssetName == "Asset1")
	{
		AssetsToDelete.Add(Asset);
		break;
	}
}
ObjectTools::DeleteAssets(AssetsToDelete, false);
return;

The callstack ends up calling into ObjectTools::DeleteSingleObject() which has an unavoidable call to:

GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->CloseAllEditorsForAsset(ObjectToDelete); which causes the crash.

Failed.png(118 KB)

[mention removed]​ Thanks for trying that out. I finally figured out what was causing the issue. It’s very strange.

To give you more context: the function that calls ObjectTools::DeleteAssets is triggered from a delegate that runs after an HTTP request.

If I move the ObjectTools::DeleteAssets before the network request everything works.

The current implementation looks like this:

MyCommandlet::Main()
{
    MyNetworkFunction();
} 
 
MyNetworkFunction()
{
	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> pRequest = SetupNetworkRequest(MyURL, TEXT("POST"), true);
	FString ResponseJson = "";
 
	pRequest->OnProcessRequestComplete().BindLambda(
		[&, ResponseJson](
			FHttpRequestPtr pRequest,
			FHttpResponsePtr pResponse,
			bool connectedSuccessfully) mutable {
				if (connectedSuccessfully) {
					if (EHttpResponseCodes::IsOk(pResponse->GetResponseCode()))
					{
						ResponseJson = pResponse->GetContentAsString();
						TSharedPtr<FJsonObject> JsonParsed;
						TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(ResponseJson);
 
						if (FJsonSerializer::Deserialize(JsonReader, JsonParsed))
						{
							SaveData = JsonParsed->GetObjectField(TEXT("d"));
							OnNetworkCallCompleted.Execute();
						}
					}
				}
		});
 
	pRequest->ProcessRequest();
}
 
// OnNetworkCallCompleted.Execute() binds to Cleanup()
 
void Cleanup()
{
    ObjectTools::DeleteAssets(AssetsToDelete, IsRunningCommandlet() == false);
}

If I call the Cleanup function directly from the commandlet, the DeleteObject() call does not fail. It only fails when executed from the network callback.

I’ve attached the full callstack as well.

Thank you so much for your time and help!

Screenshot 2026-02-25 190451.png(214 KB)

Thank you for the update.

ObjectTools should only be called from the main thread, as it is not thread-safe. This could explain the memory corruption issue.

I’m glad to hear the problem has been resolved. Please let me know if you have any further questions.

Cheers,

[mention removed]​ - I can confirm that ObjectTools still fails when forced to run from the GameThread. In my example, Cleanup() is running on the main thread - whether I execute the network call or not. It seems like the network call just puts the commandlet in an undefined state.

Unfortunately, my Cleanup operation must run after the network call, since the data we are using the determine deletion relies on an external web-hosted database.

It looks like my only option here would be to split this into two commandlets, then - one to make the network calls and one to run cleanup?

Thanks,

Steve

Hi,

I’m wondering whether the cleanup runs only after the HTTP request has fully completed, or if there might be an issue in the HTTP request implementation itself.

That said, since the deletion criteria depends on data, caching the data and splitting it into two separate commandlets is actually a very clean and robust approach.

Cheers,

Thanks for all your help [mention removed]​ ! This is the solution I’m going with for now.

Thank you. I’m glad I could help.

That sounds like a solid plan for now. I will close this case now, but don’t hesitate to reach out if anything else comes up!

Cheers,