Crash when using "Edit multiple assets"

If you are editing within a level instance and you use “Edit Multiple Assets” for multiple selected actors you can crash.

I have triaged this with Claude and have come to a clear conclusion about the crash but since I know there are rules at Epic about using agentic code, I don’t want to describe it more or provide a patch. I’m hoping the callstack and the general description is enough to identify the root issue. Happy to give more clear direction if I’m being too cautious over your policies.

[Attachment Removed]

Steps to Reproduce
Using “Edit Multiple Assets” while in edit mode for a LevelInstance can crash.

[Attachment Removed]

Hi,

So sorry for the delay; can you share the callstack of the crash? Feel free to share Claude’s analysis as well, the only thing we explicitly prohibit is Github pull requests containing AI-generated code. I haven’t had any luck reproducing this so far, but the oldest engine version I currently have is 5.7 so I can check your callstack against mine to see if it might have been fixed in a newer engine version.

Best,

Cody

[Attachment Removed]

Callstack:

UE::CoreUObject::Private::ResolveObjectHandleNoRead (ObjectHandle.h:423)

ObjectPtr_Private::Friend::NoAccessTrackingGet (ObjectPtr.h:876)

ObjectPtr_Private::TNonAccessTrackedObjectPtr<T>::Get (ObjectPtr.h:910)

* (ObjectPtr.h:935)

UStruct::GetSuperStruct (Class.h:673)

UClass::GetSuperClass (Class.h:3490)

UAssetToolsImpl::GetAssetTypeActionsForClass (AssetTools.cpp:1577)

UAssetEditorSubsystem::CanOpenEditorForAsset (AssetEditorSubsystem.cpp:1548)

UAssetEditorSubsystem::OpenEditorForAsset (AssetEditorSubsystem.cpp:505)

FLevelEditorActionCallbacks::EditAsset_Clicked (LevelEditorActions.cpp:1527)

Invoke (Invoke.h:47)

UE::Core::Private::Tuple::TTupleBase<T>::ApplyAfter (Tuple.h:320)

TBaseStaticDelegateInstance<T>::ExecuteIfSafe (DelegateInstancesImpl.h:799)

TDelegate<T>::ExecuteIfBound (DelegateSignatureImpl.inl:635)

FUIAction::Execute (UIAction.h:139)

FUICommandList::ExecuteAction (UICommandList.cpp:117)

SMenuEntryBlock::OnClicked (SMenuEntryBlock.cpp:1188)

SMenuEntryBlock::OnMenuItemButtonClicked (SMenuEntryBlock.cpp:1147)

Invoke (Invoke.h:66)

UE::Core::Private::Tuple::TTupleBase<T>::ApplyAfter (Tuple.h:320)

TBaseSPMethodDelegateInstance<T>::Execute (DelegateInstancesImpl.h:292)

TDelegate<T>::Execute (DelegateSignatureImpl.inl:614)

SButton::ExecuteOnClick (SButton.cpp:495)

Analysis of root cause w/ Claude:

Steps 1, 2 and 5 are confirmed from the dump and the source. Steps 3 and 4 are inferred from the

control flow plus the repro, and are consistent with all the evidence above, but we did not capture

a GC in the act.

**1. The array is invisible to GC.** `EditAsset_Clicked` (stock 5.6.1):

```cpp

TArray< UObject* > ReferencedAssets;

const bool bIgnoreOtherAssetsIfBPReferenced = true;

GEditor->GetReferencedAssetsForEditorSelection( ReferencedAssets, bIgnoreOtherAssetsIfBPReferenced );

// … confirmation dialog …

for (auto Asset : ReferencedAssets)

{

GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Asset, ToolkitMode, LevelEditorSharedPtr);

}

```

Plain `UObject*`, stack-local, no root, no `TObjectPtr`, no `FGCObject`. Nothing in this function

keeps any entry alive.

**2. For a level instance, the entry is the actor itself — not an asset.**

`UEditorEngine::GetReferencedAssetsForEditorSelection` (`EditorEngine.cpp:3429`) walks the selection

calling `AActor::GetReferencedContentObjects`, and `ALevelInstance` overrides it as

(`LevelInstanceActor.cpp:353`):

```cpp

bool ALevelInstance::GetReferencedContentObjects(TArray<UObject*>& Objects) const

{

Objects.Add(const_cast<ALevelInstance*>(this));

return true;

}

```

So despite the variable being named `ReferencedAssets`, the entries are world actors whose lifetime

is bound to level streaming, not to asset loading. This is what makes the window wide enough to hit

in practice: an asset would normally survive the loop because something else has it loaded, whereas

these actors are destroyed by the very operation the loop is performing.

**3. Opening the first entry removes every root holding the rest.**

`OpenEditorForAsset(ALevelInstance*)` → `ALevelInstance::OpenAssetEditor()` → `EnterEdit()` →

`ULevelInstanceSubsystem::EditLevelInstanceInternal` (`LevelInstanceSubsystem.cpp:2456`), which does:

- `GEditor->SelectNone(true, true)` (`:2494`) — drops the `USelection` references. The selection was

the strong reference the array was silently relying on.

- `UnloadLevelInstance(EditLevelInstanceID)` (`:2533`, under the comment “Unload right away”) —

destroys the actors in the unloaded level, which is where the other selected nested level

instances live.

- `GEditor->ResetTransaction(…)` (`:2570`) — drops the undo buffer’s references.

**4. The same function pumps the message loop, so GC can actually run.**

- `FScopedSlowTask SlowTask(…); SlowTask.MakeDialog();` (`:2487-2488`).

- `PromptUserForCommit(…)` (`:2472`) — a modal dialog when a prior edit is dirty.

- `GEngine->BlockTillLevelStreamingCompleted(…)` via `ULevelStreamingLevelInstanceEditor::Load`

(`LevelInstanceEditorLevelStreaming.cpp:122`).

- On the property-override path, `CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true)` outright

(`LevelInstanceEditorPropertyOverrideLevelStreaming.cpp:477`).

With `Ctrl+E` there is an additional pump before the loop even starts, from the “open them all?”

`FMessageDialog`. `Ctrl+Shift+E` skips that one, and still crashes.

**5. The next iteration dereferences freed memory.** `CanOpenEditorForAsset` only null-checks:

```cpp

if (!Asset)

{

…

return false;

}

FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT(“AssetTools”));

TWeakPtr<IAssetTypeActions> AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(Asset->GetClass());

```

`Asset->GetClass()` reads the reused block, returns `0x614D67646C425F6C`, and

`GetAssetTypeActionsForClass` walks it. Crash.

This is why a multi-selection is required to hit the bug: with a single entry the loop never returns

to the array after the pump.

[Attachment Removed]

Hi,

I was able to get a repro with the same callstack, and I believe it should be fixed now in CL#56557309. Let me know if you still run into the same crash with that change!

Best,

Cody

[Attachment Removed]